FSTLocalStoreTests.mm 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "Firestore/Source/Local/FSTLocalStore.h"
  17. #import <FirebaseFirestore/FIRTimestamp.h>
  18. #import <XCTest/XCTest.h>
  19. #include <string>
  20. #include <utility>
  21. #include <vector>
  22. #import "Firestore/Source/API/FIRFieldValue+Internal.h"
  23. #import "Firestore/Source/Local/FSTPersistence.h"
  24. #import "Firestore/Source/Util/FSTClasses.h"
  25. #import "Firestore/Example/Tests/Local/FSTLocalStoreTests.h"
  26. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  27. #include "Firestore/core/include/firebase/firestore/timestamp.h"
  28. #include "Firestore/core/src/firebase/firestore/auth/user.h"
  29. #include "Firestore/core/src/firebase/firestore/local/local_view_changes.h"
  30. #include "Firestore/core/src/firebase/firestore/local/local_write_result.h"
  31. #include "Firestore/core/src/firebase/firestore/local/query_data.h"
  32. #include "Firestore/core/src/firebase/firestore/model/document_map.h"
  33. #include "Firestore/core/src/firebase/firestore/model/document_set.h"
  34. #include "Firestore/core/src/firebase/firestore/model/mutation_batch_result.h"
  35. #include "Firestore/core/src/firebase/firestore/remote/remote_event.h"
  36. #include "Firestore/core/src/firebase/firestore/remote/watch_change.h"
  37. #include "Firestore/core/src/firebase/firestore/util/status.h"
  38. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  39. namespace testutil = firebase::firestore::testutil;
  40. using firebase::Timestamp;
  41. using firebase::firestore::auth::User;
  42. using firebase::firestore::local::LocalViewChanges;
  43. using firebase::firestore::local::LocalWriteResult;
  44. using firebase::firestore::local::QueryData;
  45. using firebase::firestore::model::Document;
  46. using firebase::firestore::model::DocumentKey;
  47. using firebase::firestore::model::DocumentKeySet;
  48. using firebase::firestore::model::DocumentState;
  49. using firebase::firestore::model::FieldValue;
  50. using firebase::firestore::model::ListenSequenceNumber;
  51. using firebase::firestore::model::MaybeDocument;
  52. using firebase::firestore::model::Mutation;
  53. using firebase::firestore::model::MutationBatch;
  54. using firebase::firestore::model::MutationBatchResult;
  55. using firebase::firestore::model::MutationResult;
  56. using firebase::firestore::model::DocumentMap;
  57. using firebase::firestore::model::MaybeDocumentMap;
  58. using firebase::firestore::model::SnapshotVersion;
  59. using firebase::firestore::model::TargetId;
  60. using firebase::firestore::nanopb::ByteString;
  61. using firebase::firestore::remote::RemoteEvent;
  62. using firebase::firestore::remote::TestTargetMetadataProvider;
  63. using firebase::firestore::remote::WatchChangeAggregator;
  64. using firebase::firestore::remote::WatchTargetChange;
  65. using firebase::firestore::remote::WatchTargetChangeState;
  66. using firebase::firestore::util::Status;
  67. using testutil::Array;
  68. using testutil::DeletedDoc;
  69. using testutil::Doc;
  70. using testutil::Key;
  71. using testutil::Map;
  72. using testutil::Query;
  73. using testutil::UnknownDoc;
  74. using testutil::Vector;
  75. namespace {
  76. std::vector<MaybeDocument> DocMapToArray(const MaybeDocumentMap &docs) {
  77. std::vector<MaybeDocument> result;
  78. for (const auto &kv : docs) {
  79. result.push_back(kv.second);
  80. }
  81. return result;
  82. }
  83. std::vector<Document> DocMapToArray(const DocumentMap &docs) {
  84. std::vector<Document> result;
  85. for (const auto &kv : docs.underlying_map()) {
  86. result.push_back(Document(kv.second));
  87. }
  88. return result;
  89. }
  90. } // namespace
  91. NS_ASSUME_NONNULL_BEGIN
  92. @interface FSTLocalStoreTests ()
  93. @property(nonatomic, strong, readwrite) id<FSTPersistence> localStorePersistence;
  94. @property(nonatomic, strong, readwrite) FSTLocalStore *localStore;
  95. @property(nonatomic, assign, readwrite) TargetId lastTargetID;
  96. @end
  97. @implementation FSTLocalStoreTests {
  98. std::vector<MutationBatch> _batches;
  99. MaybeDocumentMap _lastChanges;
  100. }
  101. - (void)setUp {
  102. [super setUp];
  103. if ([self isTestBaseClass]) {
  104. return;
  105. }
  106. id<FSTPersistence> persistence = [self persistence];
  107. self.localStorePersistence = persistence;
  108. self.localStore = [[FSTLocalStore alloc] initWithPersistence:persistence
  109. initialUser:User::Unauthenticated()];
  110. [self.localStore start];
  111. _lastTargetID = 0;
  112. }
  113. - (void)tearDown {
  114. [self.localStorePersistence shutdown];
  115. [super tearDown];
  116. }
  117. - (id<FSTPersistence>)persistence {
  118. @throw FSTAbstractMethodException(); // NOLINT
  119. }
  120. - (BOOL)gcIsEager {
  121. @throw FSTAbstractMethodException(); // NOLINT
  122. }
  123. /**
  124. * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
  125. * FSTLocalStoreTests since it is incomplete without the implementations supplied by its
  126. * subclasses.
  127. */
  128. - (BOOL)isTestBaseClass {
  129. return [self class] == [FSTLocalStoreTests class];
  130. }
  131. - (void)writeMutation:(Mutation)mutation {
  132. [self writeMutations:{std::move(mutation)}];
  133. }
  134. - (void)writeMutations:(std::vector<Mutation> &&)mutations {
  135. auto mutationsCopy = mutations;
  136. LocalWriteResult result = [self.localStore locallyWriteMutations:std::move(mutationsCopy)];
  137. _batches.emplace_back(result.batch_id(), Timestamp::Now(), std::vector<Mutation>{},
  138. std::move(mutations));
  139. _lastChanges = result.changes();
  140. }
  141. - (void)applyRemoteEvent:(const RemoteEvent &)event {
  142. _lastChanges = [self.localStore applyRemoteEvent:event];
  143. }
  144. - (void)notifyLocalViewChanges:(LocalViewChanges)changes {
  145. [self.localStore notifyLocalViewChanges:std::vector<LocalViewChanges>{std::move(changes)}];
  146. }
  147. - (void)acknowledgeMutationWithVersion:(FSTTestSnapshotVersion)documentVersion
  148. transformResult:(id _Nullable)transformResult {
  149. XCTAssertGreaterThan(_batches.size(), 0, @"Missing batch to acknowledge.");
  150. MutationBatch batch = _batches.front();
  151. _batches.erase(_batches.begin());
  152. XCTAssertEqual(batch.mutations().size(), 1,
  153. @"Acknowledging more than one mutation not supported.");
  154. SnapshotVersion version = testutil::Version(documentVersion);
  155. absl::optional<std::vector<FieldValue>> mutationTransformResult;
  156. if (transformResult) {
  157. mutationTransformResult = std::vector<FieldValue>{FSTTestFieldValue(transformResult)};
  158. }
  159. MutationResult mutationResult(version, mutationTransformResult);
  160. MutationBatchResult result(batch, version, {mutationResult}, {});
  161. _lastChanges = [self.localStore acknowledgeBatchWithResult:result];
  162. }
  163. - (void)acknowledgeMutationWithVersion:(FSTTestSnapshotVersion)documentVersion {
  164. [self acknowledgeMutationWithVersion:documentVersion transformResult:nil];
  165. }
  166. - (void)rejectMutation {
  167. MutationBatch batch = _batches.front();
  168. _batches.erase(_batches.begin());
  169. _lastChanges = [self.localStore rejectBatchID:batch.batch_id()];
  170. }
  171. - (TargetId)allocateQuery:(core::Query)query {
  172. QueryData queryData = [self.localStore allocateQuery:std::move(query)];
  173. self.lastTargetID = queryData.target_id();
  174. return queryData.target_id();
  175. }
  176. /** Asserts that the last target ID is the given number. */
  177. #define FSTAssertTargetID(targetID) \
  178. do { \
  179. XCTAssertEqual(self.lastTargetID, targetID); \
  180. } while (0)
  181. /** Asserts that a the lastChanges contain the docs in the given array. */
  182. #define FSTAssertChanged(...) \
  183. do { \
  184. std::vector<MaybeDocument> expected = {__VA_ARGS__}; \
  185. XCTAssertEqual(_lastChanges.size(), expected.size()); \
  186. auto lastChangesList = DocMapToArray(_lastChanges); \
  187. XCTAssertEqual(lastChangesList, expected); \
  188. _lastChanges = MaybeDocumentMap{}; \
  189. } while (0)
  190. /** Asserts that the given keys were removed. */
  191. #define FSTAssertRemoved(...) \
  192. do { \
  193. std::vector<std::string> keyPaths = {__VA_ARGS__}; \
  194. XCTAssertEqual(_lastChanges.size(), keyPaths.size()); \
  195. auto keyPathIterator = keyPaths.begin(); \
  196. for (const auto &kv : _lastChanges) { \
  197. const DocumentKey &actualKey = kv.first; \
  198. const MaybeDocument &value = kv.second; \
  199. DocumentKey expectedKey = Key(*keyPathIterator); \
  200. XCTAssertEqual(actualKey, expectedKey); \
  201. XCTAssertTrue(value.is_no_document()); \
  202. ++keyPathIterator; \
  203. } \
  204. _lastChanges = MaybeDocumentMap{}; \
  205. } while (0)
  206. /** Asserts that the given local store contains the given document. */
  207. #define FSTAssertContains(document) \
  208. do { \
  209. MaybeDocument expected = (document); \
  210. absl::optional<MaybeDocument> actual = [self.localStore readDocument:expected.key()]; \
  211. XCTAssertEqual(actual, expected); \
  212. } while (0)
  213. /** Asserts that the given local store does not contain the given document. */
  214. #define FSTAssertNotContains(keyPathString) \
  215. do { \
  216. DocumentKey key = Key(keyPathString); \
  217. absl::optional<MaybeDocument> actual = [self.localStore readDocument:key]; \
  218. XCTAssertEqual(actual, absl::nullopt); \
  219. } while (0)
  220. - (void)testMutationBatchKeys {
  221. if ([self isTestBaseClass]) return;
  222. Mutation base = FSTTestSetMutation(@"foo/ignore", @{@"foo" : @"bar"});
  223. Mutation set1 = FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"});
  224. Mutation set2 = FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"});
  225. MutationBatch batch = MutationBatch(1, Timestamp::Now(), {base}, {set1, set2});
  226. DocumentKeySet keys = batch.keys();
  227. XCTAssertEqual(keys.size(), 2u);
  228. }
  229. - (void)testHandlesSetMutation {
  230. if ([self isTestBaseClass]) return;
  231. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  232. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  233. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  234. [self acknowledgeMutationWithVersion:0];
  235. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kCommittedMutations));
  236. if ([self gcIsEager]) {
  237. // Nothing is pinning this anymore, as it has been acknowledged and there are no targets active.
  238. FSTAssertNotContains("foo/bar");
  239. } else {
  240. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kCommittedMutations));
  241. }
  242. }
  243. - (void)testHandlesSetMutationThenDocument {
  244. if ([self isTestBaseClass]) return;
  245. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  246. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  247. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  248. TargetId targetID = [self allocateQuery:Query("foo")];
  249. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 2, Map("it", "changed")),
  250. {targetID}, {})];
  251. FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations));
  252. FSTAssertContains(Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations));
  253. }
  254. - (void)testHandlesAckThenRejectThenRemoteEvent {
  255. if ([self isTestBaseClass]) return;
  256. // Start a query that requires acks to be held.
  257. core::Query query = Query("foo");
  258. TargetId targetID = [self allocateQuery:query];
  259. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  260. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  261. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  262. // The last seen version is zero, so this ack must be held.
  263. [self acknowledgeMutationWithVersion:1];
  264. FSTAssertChanged(Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kCommittedMutations));
  265. // Under eager GC, there is no longer a reference for the document, and it should be
  266. // deleted.
  267. if ([self gcIsEager]) {
  268. FSTAssertNotContains("foo/bar");
  269. } else {
  270. FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kCommittedMutations));
  271. }
  272. [self writeMutation:FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"})];
  273. FSTAssertChanged(Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations));
  274. FSTAssertContains(Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations));
  275. [self rejectMutation];
  276. FSTAssertRemoved("bar/baz");
  277. FSTAssertNotContains("bar/baz");
  278. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 2, Map("it", "changed")),
  279. {targetID})];
  280. FSTAssertChanged(Doc("foo/bar", 2, Map("it", "changed")));
  281. FSTAssertContains(Doc("foo/bar", 2, Map("it", "changed")));
  282. FSTAssertNotContains("bar/baz");
  283. }
  284. - (void)testHandlesDeletedDocumentThenSetMutationThenAck {
  285. if ([self isTestBaseClass]) return;
  286. core::Query query = Query("foo");
  287. TargetId targetID = [self allocateQuery:query];
  288. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(DeletedDoc("foo/bar", 2), {targetID}, {})];
  289. FSTAssertRemoved("foo/bar");
  290. // Under eager GC, there is no longer a reference for the document, and it should be
  291. // deleted.
  292. if (![self gcIsEager]) {
  293. FSTAssertContains(DeletedDoc("foo/bar", 2, NO));
  294. } else {
  295. FSTAssertNotContains("foo/bar");
  296. }
  297. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  298. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  299. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  300. // Can now remove the target, since we have a mutation pinning the document
  301. [self.localStore releaseQuery:query];
  302. // Verify we didn't lose anything
  303. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  304. [self acknowledgeMutationWithVersion:3];
  305. FSTAssertChanged(Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations));
  306. // It has been acknowledged, and should no longer be retained as there is no target and mutation
  307. if ([self gcIsEager]) {
  308. FSTAssertNotContains("foo/bar");
  309. }
  310. }
  311. - (void)testHandlesSetMutationThenDeletedDocument {
  312. if ([self isTestBaseClass]) return;
  313. core::Query query = Query("foo");
  314. TargetId targetID = [self allocateQuery:query];
  315. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  316. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  317. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(DeletedDoc("foo/bar", 2), {targetID}, {})];
  318. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  319. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  320. }
  321. - (void)testHandlesDocumentThenSetMutationThenAckThenDocument {
  322. if ([self isTestBaseClass]) return;
  323. // Start a query that requires acks to be held.
  324. core::Query query = Query("foo");
  325. TargetId targetID = [self allocateQuery:query];
  326. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 2, Map("it", "base")), {targetID})];
  327. FSTAssertChanged(Doc("foo/bar", 2, Map("it", "base")));
  328. FSTAssertContains(Doc("foo/bar", 2, Map("it", "base")));
  329. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  330. FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations));
  331. FSTAssertContains(Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations));
  332. [self acknowledgeMutationWithVersion:3];
  333. // we haven't seen the remote event yet, so the write is still held.
  334. FSTAssertChanged(Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations));
  335. FSTAssertContains(Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations));
  336. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 3, Map("it", "changed")),
  337. {targetID}, {})];
  338. FSTAssertChanged(Doc("foo/bar", 3, Map("it", "changed")));
  339. FSTAssertContains(Doc("foo/bar", 3, Map("it", "changed")));
  340. }
  341. - (void)testHandlesPatchWithoutPriorDocument {
  342. if ([self isTestBaseClass]) return;
  343. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  344. FSTAssertRemoved("foo/bar");
  345. FSTAssertNotContains("foo/bar");
  346. [self acknowledgeMutationWithVersion:1];
  347. FSTAssertChanged(UnknownDoc("foo/bar", 1));
  348. if ([self gcIsEager]) {
  349. FSTAssertNotContains("foo/bar");
  350. } else {
  351. FSTAssertContains(UnknownDoc("foo/bar", 1));
  352. }
  353. }
  354. - (void)testHandlesPatchMutationThenDocumentThenAck {
  355. if ([self isTestBaseClass]) return;
  356. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  357. FSTAssertRemoved("foo/bar");
  358. FSTAssertNotContains("foo/bar");
  359. core::Query query = Query("foo");
  360. TargetId targetID = [self allocateQuery:query];
  361. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {targetID})];
  362. FSTAssertChanged(
  363. Doc("foo/bar", 1, Map("foo", "bar", "it", "base"), DocumentState::kLocalMutations));
  364. FSTAssertContains(
  365. Doc("foo/bar", 1, Map("foo", "bar", "it", "base"), DocumentState::kLocalMutations));
  366. [self acknowledgeMutationWithVersion:2];
  367. // We still haven't seen the remote events for the patch, so the local changes remain, and there
  368. // are no changes
  369. FSTAssertChanged(
  370. Doc("foo/bar", 2, Map("foo", "bar", "it", "base"), DocumentState::kCommittedMutations));
  371. FSTAssertContains(
  372. Doc("foo/bar", 2, Map("foo", "bar", "it", "base"), DocumentState::kCommittedMutations));
  373. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  374. Doc("foo/bar", 2, Map("foo", "bar", "it", "base")), {targetID}, {})];
  375. FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar", "it", "base")));
  376. FSTAssertContains(Doc("foo/bar", 2, Map("foo", "bar", "it", "base")));
  377. }
  378. - (void)testHandlesPatchMutationThenAckThenDocument {
  379. if ([self isTestBaseClass]) return;
  380. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  381. FSTAssertRemoved("foo/bar");
  382. FSTAssertNotContains("foo/bar");
  383. [self acknowledgeMutationWithVersion:1];
  384. FSTAssertChanged(UnknownDoc("foo/bar", 1));
  385. // There's no target pinning the doc, and we've ack'd the mutation.
  386. if ([self gcIsEager]) {
  387. FSTAssertNotContains("foo/bar");
  388. } else {
  389. FSTAssertContains(UnknownDoc("foo/bar", 1));
  390. }
  391. core::Query query = Query("foo");
  392. TargetId targetID = [self allocateQuery:query];
  393. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {targetID},
  394. {})];
  395. FSTAssertChanged(Doc("foo/bar", 1, Map("it", "base")));
  396. FSTAssertContains(Doc("foo/bar", 1, Map("it", "base")));
  397. }
  398. - (void)testHandlesDeleteMutationThenAck {
  399. if ([self isTestBaseClass]) return;
  400. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  401. FSTAssertRemoved("foo/bar");
  402. FSTAssertContains(DeletedDoc("foo/bar"));
  403. [self acknowledgeMutationWithVersion:1];
  404. FSTAssertRemoved("foo/bar");
  405. // There's no target pinning the doc, and we've ack'd the mutation.
  406. if ([self gcIsEager]) {
  407. FSTAssertNotContains("foo/bar");
  408. }
  409. }
  410. - (void)testHandlesDocumentThenDeleteMutationThenAck {
  411. if ([self isTestBaseClass]) return;
  412. core::Query query = Query("foo");
  413. TargetId targetID = [self allocateQuery:query];
  414. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {targetID},
  415. {})];
  416. FSTAssertChanged(Doc("foo/bar", 1, Map("it", "base")));
  417. FSTAssertContains(Doc("foo/bar", 1, Map("it", "base")));
  418. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  419. FSTAssertRemoved("foo/bar");
  420. FSTAssertContains(DeletedDoc("foo/bar"));
  421. // Remove the target so only the mutation is pinning the document
  422. [self.localStore releaseQuery:query];
  423. [self acknowledgeMutationWithVersion:2];
  424. FSTAssertRemoved("foo/bar");
  425. if ([self gcIsEager]) {
  426. // Neither the target nor the mutation pin the document, it should be gone.
  427. FSTAssertNotContains("foo/bar");
  428. }
  429. }
  430. - (void)testHandlesDeleteMutationThenDocumentThenAck {
  431. if ([self isTestBaseClass]) return;
  432. core::Query query = Query("foo");
  433. TargetId targetID = [self allocateQuery:query];
  434. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  435. FSTAssertRemoved("foo/bar");
  436. FSTAssertContains(DeletedDoc("foo/bar"));
  437. // Add the document to a target so it will remain in persistence even when ack'd
  438. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {targetID},
  439. {})];
  440. FSTAssertRemoved("foo/bar");
  441. FSTAssertContains(DeletedDoc("foo/bar"));
  442. // Don't need to keep it pinned anymore
  443. [self.localStore releaseQuery:query];
  444. [self acknowledgeMutationWithVersion:2];
  445. FSTAssertRemoved("foo/bar");
  446. if ([self gcIsEager]) {
  447. // The doc is not pinned in a target and we've acknowledged the mutation. It shouldn't exist
  448. // anymore.
  449. FSTAssertNotContains("foo/bar");
  450. }
  451. }
  452. - (void)testHandlesDocumentThenDeletedDocumentThenDocument {
  453. if ([self isTestBaseClass]) return;
  454. core::Query query = Query("foo");
  455. TargetId targetID = [self allocateQuery:query];
  456. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {targetID},
  457. {})];
  458. FSTAssertChanged(Doc("foo/bar", 1, Map("it", "base")));
  459. FSTAssertContains(Doc("foo/bar", 1, Map("it", "base")));
  460. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(DeletedDoc("foo/bar", 2), {targetID}, {})];
  461. FSTAssertRemoved("foo/bar");
  462. if (![self gcIsEager]) {
  463. FSTAssertContains(DeletedDoc("foo/bar", 2));
  464. }
  465. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 3, Map("it", "changed")),
  466. {targetID}, {})];
  467. FSTAssertChanged(Doc("foo/bar", 3, Map("it", "changed")));
  468. FSTAssertContains(Doc("foo/bar", 3, Map("it", "changed")));
  469. }
  470. - (void)testHandlesSetMutationThenPatchMutationThenDocumentThenAckThenAck {
  471. if ([self isTestBaseClass]) return;
  472. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
  473. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "old"), DocumentState::kLocalMutations));
  474. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "old"), DocumentState::kLocalMutations));
  475. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  476. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  477. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  478. core::Query query = Query("foo");
  479. TargetId targetID = [self allocateQuery:query];
  480. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {targetID},
  481. {})];
  482. FSTAssertChanged(Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kLocalMutations));
  483. FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kLocalMutations));
  484. [self.localStore releaseQuery:query];
  485. [self acknowledgeMutationWithVersion:2]; // delete mutation
  486. FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations));
  487. FSTAssertContains(Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations));
  488. [self acknowledgeMutationWithVersion:3]; // patch mutation
  489. FSTAssertChanged(Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations));
  490. if ([self gcIsEager]) {
  491. // we've ack'd all of the mutations, nothing is keeping this pinned anymore
  492. FSTAssertNotContains("foo/bar");
  493. } else {
  494. FSTAssertContains(Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations));
  495. }
  496. }
  497. - (void)testHandlesSetMutationAndPatchMutationTogether {
  498. if ([self isTestBaseClass]) return;
  499. [self writeMutations:{
  500. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
  501. FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})
  502. }];
  503. FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  504. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  505. }
  506. - (void)testHandlesSetMutationThenPatchMutationThenReject {
  507. if ([self isTestBaseClass]) return;
  508. if (![self gcIsEager]) return;
  509. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
  510. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "old"), DocumentState::kLocalMutations));
  511. [self acknowledgeMutationWithVersion:1];
  512. FSTAssertNotContains("foo/bar");
  513. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  514. // A blind patch is not visible in the cache
  515. FSTAssertNotContains("foo/bar");
  516. [self rejectMutation];
  517. FSTAssertNotContains("foo/bar");
  518. }
  519. - (void)testHandlesSetMutationsAndPatchMutationOfJustOneTogether {
  520. if ([self isTestBaseClass]) return;
  521. [self writeMutations:{
  522. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
  523. FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"}),
  524. FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})
  525. }];
  526. FSTAssertChanged(Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations),
  527. Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  528. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  529. FSTAssertContains(Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations));
  530. }
  531. - (void)testHandlesDeleteMutationThenPatchMutationThenAckThenAck {
  532. if ([self isTestBaseClass]) return;
  533. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  534. FSTAssertRemoved("foo/bar");
  535. FSTAssertContains(DeletedDoc("foo/bar"));
  536. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  537. FSTAssertRemoved("foo/bar");
  538. FSTAssertContains(DeletedDoc("foo/bar"));
  539. [self acknowledgeMutationWithVersion:2]; // delete mutation
  540. FSTAssertRemoved("foo/bar");
  541. FSTAssertContains(DeletedDoc("foo/bar", 2, /* has_committed_mutations= */ true));
  542. [self acknowledgeMutationWithVersion:3]; // patch mutation
  543. FSTAssertChanged(UnknownDoc("foo/bar", 3));
  544. if ([self gcIsEager]) {
  545. // There are no more pending mutations, the doc has been dropped
  546. FSTAssertNotContains("foo/bar");
  547. } else {
  548. FSTAssertContains(UnknownDoc("foo/bar", 3));
  549. }
  550. }
  551. - (void)testCollectsGarbageAfterChangeBatchWithNoTargetIDs {
  552. if ([self isTestBaseClass]) return;
  553. if (![self gcIsEager]) return;
  554. [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets(DeletedDoc("foo/bar", 2), {}, {},
  555. {1})];
  556. FSTAssertNotContains("foo/bar");
  557. [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets(
  558. Doc("foo/bar", 2, Map("foo", "bar")), {}, {}, {1})];
  559. FSTAssertNotContains("foo/bar");
  560. }
  561. - (void)testCollectsGarbageAfterChangeBatch {
  562. if ([self isTestBaseClass]) return;
  563. if (![self gcIsEager]) return;
  564. core::Query query = Query("foo");
  565. TargetId targetID = [self allocateQuery:query];
  566. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 2, Map("foo", "bar")), {targetID})];
  567. FSTAssertContains(Doc("foo/bar", 2, Map("foo", "bar")));
  568. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 2, Map("foo", "baz")), {},
  569. {targetID})];
  570. FSTAssertNotContains("foo/bar");
  571. }
  572. - (void)testCollectsGarbageAfterAcknowledgedMutation {
  573. if ([self isTestBaseClass]) return;
  574. if (![self gcIsEager]) return;
  575. core::Query query = Query("foo");
  576. TargetId targetID = [self allocateQuery:query];
  577. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 0, Map("foo", "old")), {targetID},
  578. {})];
  579. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  580. // Release the query so that our target count goes back to 0 and we are considered up-to-date.
  581. [self.localStore releaseQuery:query];
  582. [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
  583. [self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
  584. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  585. FSTAssertContains(Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations));
  586. FSTAssertContains(DeletedDoc("foo/baz"));
  587. [self acknowledgeMutationWithVersion:3];
  588. FSTAssertNotContains("foo/bar");
  589. FSTAssertContains(Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations));
  590. FSTAssertContains(DeletedDoc("foo/baz"));
  591. [self acknowledgeMutationWithVersion:4];
  592. FSTAssertNotContains("foo/bar");
  593. FSTAssertNotContains("foo/bah");
  594. FSTAssertContains(DeletedDoc("foo/baz"));
  595. [self acknowledgeMutationWithVersion:5];
  596. FSTAssertNotContains("foo/bar");
  597. FSTAssertNotContains("foo/bah");
  598. FSTAssertNotContains("foo/baz");
  599. }
  600. - (void)testCollectsGarbageAfterRejectedMutation {
  601. if ([self isTestBaseClass]) return;
  602. if (![self gcIsEager]) return;
  603. core::Query query = Query("foo");
  604. TargetId targetID = [self allocateQuery:query];
  605. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 0, Map("foo", "old")), {targetID},
  606. {})];
  607. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  608. // Release the query so that our target count goes back to 0 and we are considered up-to-date.
  609. [self.localStore releaseQuery:query];
  610. [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
  611. [self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
  612. FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations));
  613. FSTAssertContains(Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations));
  614. FSTAssertContains(DeletedDoc("foo/baz"));
  615. [self rejectMutation]; // patch mutation
  616. FSTAssertNotContains("foo/bar");
  617. FSTAssertContains(Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations));
  618. FSTAssertContains(DeletedDoc("foo/baz"));
  619. [self rejectMutation]; // set mutation
  620. FSTAssertNotContains("foo/bar");
  621. FSTAssertNotContains("foo/bah");
  622. FSTAssertContains(DeletedDoc("foo/baz"));
  623. [self rejectMutation]; // delete mutation
  624. FSTAssertNotContains("foo/bar");
  625. FSTAssertNotContains("foo/bah");
  626. FSTAssertNotContains("foo/baz");
  627. }
  628. - (void)testPinsDocumentsInTheLocalView {
  629. if ([self isTestBaseClass]) return;
  630. if (![self gcIsEager]) return;
  631. core::Query query = Query("foo");
  632. TargetId targetID = [self allocateQuery:query];
  633. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 1, Map("foo", "bar")), {targetID})];
  634. [self writeMutation:FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"})];
  635. FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar")));
  636. FSTAssertContains(Doc("foo/baz", 0, Map("foo", "baz"), DocumentState::kLocalMutations));
  637. [self notifyLocalViewChanges:TestViewChanges(targetID, @[ @"foo/bar", @"foo/baz" ], @[])];
  638. FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar")));
  639. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 1, Map("foo", "bar")), {},
  640. {targetID})];
  641. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/baz", 2, Map("foo", "baz")), {targetID},
  642. {})];
  643. FSTAssertContains(Doc("foo/baz", 2, Map("foo", "baz"), DocumentState::kLocalMutations));
  644. [self acknowledgeMutationWithVersion:2];
  645. FSTAssertContains(Doc("foo/baz", 2, Map("foo", "baz")));
  646. FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar")));
  647. FSTAssertContains(Doc("foo/baz", 2, Map("foo", "baz")));
  648. [self notifyLocalViewChanges:TestViewChanges(targetID, @[], @[ @"foo/bar", @"foo/baz" ])];
  649. FSTAssertNotContains("foo/bar");
  650. FSTAssertNotContains("foo/baz");
  651. [self.localStore releaseQuery:query];
  652. }
  653. - (void)testThrowsAwayDocumentsWithUnknownTargetIDsImmediately {
  654. if ([self isTestBaseClass]) return;
  655. if (![self gcIsEager]) return;
  656. TargetId targetID = 321;
  657. [self applyRemoteEvent:FSTTestUpdateRemoteEventWithLimboTargets(Doc("foo/bar", 1, Map()), {}, {},
  658. {targetID})];
  659. FSTAssertNotContains("foo/bar");
  660. }
  661. - (void)testCanExecuteDocumentQueries {
  662. if ([self isTestBaseClass]) return;
  663. [self.localStore locallyWriteMutations:{
  664. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"}),
  665. FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
  666. FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"})
  667. }];
  668. core::Query query = Query("foo/bar");
  669. DocumentMap docs = [self.localStore executeQuery:query];
  670. XCTAssertEqual(DocMapToArray(docs),
  671. Vector(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)));
  672. }
  673. - (void)testCanExecuteCollectionQueries {
  674. if ([self isTestBaseClass]) return;
  675. [self.localStore locallyWriteMutations:{
  676. FSTTestSetMutation(@"fo/bar", @{@"fo" : @"bar"}),
  677. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"}),
  678. FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
  679. FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"}),
  680. FSTTestSetMutation(@"fooo/blah", @{@"fooo" : @"blah"})
  681. }];
  682. core::Query query = Query("foo");
  683. DocumentMap docs = [self.localStore executeQuery:query];
  684. XCTAssertEqual(DocMapToArray(docs),
  685. Vector(Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations),
  686. Doc("foo/baz", 0, Map("foo", "baz"), DocumentState::kLocalMutations)));
  687. }
  688. - (void)testCanExecuteMixedCollectionQueries {
  689. if ([self isTestBaseClass]) return;
  690. core::Query query = Query("foo");
  691. [self allocateQuery:query];
  692. FSTAssertTargetID(2);
  693. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/baz", 10, Map("a", "b")), {2}, {})];
  694. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 20, Map("a", "b")), {2}, {})];
  695. [self.localStore locallyWriteMutations:{ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) }];
  696. DocumentMap docs = [self.localStore executeQuery:query];
  697. XCTAssertEqual(DocMapToArray(docs),
  698. Vector(Doc("foo/bar", 20, Map("a", "b")), Doc("foo/baz", 10, Map("a", "b")),
  699. Doc("foo/bonk", 0, Map("a", "b"), DocumentState::kLocalMutations)));
  700. }
  701. - (void)testPersistsResumeTokens {
  702. if ([self isTestBaseClass]) return;
  703. // This test only works in the absence of the FSTEagerGarbageCollector.
  704. if ([self gcIsEager]) return;
  705. core::Query query = Query("foo/bar");
  706. QueryData queryData = [self.localStore allocateQuery:query];
  707. ListenSequenceNumber initialSequenceNumber = queryData.sequence_number();
  708. TargetId targetID = queryData.target_id();
  709. ByteString resumeToken = testutil::ResumeToken(1000);
  710. WatchTargetChange watchChange{WatchTargetChangeState::Current, {targetID}, resumeToken};
  711. auto metadataProvider = TestTargetMetadataProvider::CreateSingleResultProvider(
  712. testutil::Key("foo/bar"), std::vector<TargetId>{targetID});
  713. WatchChangeAggregator aggregator{&metadataProvider};
  714. aggregator.HandleTargetChange(watchChange);
  715. RemoteEvent remoteEvent = aggregator.CreateRemoteEvent(testutil::Version(1000));
  716. [self applyRemoteEvent:remoteEvent];
  717. // Stop listening so that the query should become inactive (but persistent)
  718. [self.localStore releaseQuery:query];
  719. // Should come back with the same resume token
  720. QueryData queryData2 = [self.localStore allocateQuery:query];
  721. XCTAssertEqual(queryData2.resume_token(), resumeToken);
  722. // The sequence number should have been bumped when we saved the new resume token.
  723. ListenSequenceNumber newSequenceNumber = queryData2.sequence_number();
  724. XCTAssertGreaterThan(newSequenceNumber, initialSequenceNumber);
  725. }
  726. - (void)testRemoteDocumentKeysForTarget {
  727. if ([self isTestBaseClass]) return;
  728. core::Query query = Query("foo");
  729. [self allocateQuery:query];
  730. FSTAssertTargetID(2);
  731. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/baz", 10, Map("a", "b")), {2})];
  732. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 20, Map("a", "b")), {2})];
  733. [self.localStore locallyWriteMutations:{ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) }];
  734. DocumentKeySet keys = [self.localStore remoteDocumentKeysForTarget:2];
  735. DocumentKeySet expected{testutil::Key("foo/bar"), testutil::Key("foo/baz")};
  736. XCTAssertEqual(keys, expected);
  737. keys = [self.localStore remoteDocumentKeysForTarget:2];
  738. XCTAssertEqual(keys, (DocumentKeySet{testutil::Key("foo/bar"), testutil::Key("foo/baz")}));
  739. }
  740. // TODO(mrschmidt): The FieldValue.increment() field transform tests below would probably be
  741. // better implemented as spec tests but currently they don't support transforms.
  742. - (void)testHandlesSetMutationThenTransformMutationThenTransformMutation {
  743. if ([self isTestBaseClass]) return;
  744. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"sum" : @0})];
  745. FSTAssertContains(Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations));
  746. FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations));
  747. [self writeMutation:FSTTestTransformMutation(
  748. @"foo/bar", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:1]})];
  749. FSTAssertContains(Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations));
  750. FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations));
  751. [self writeMutation:FSTTestTransformMutation(
  752. @"foo/bar", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:2]})];
  753. FSTAssertContains(Doc("foo/bar", 0, Map("sum", 3), DocumentState::kLocalMutations));
  754. FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 3), DocumentState::kLocalMutations));
  755. }
  756. - (void)testHandlesSetMutationThenAckThenTransformMutationThenAckThenTransformMutation {
  757. if ([self isTestBaseClass]) return;
  758. // Since this test doesn't start a listen, Eager GC removes the documents from the cache as
  759. // soon as the mutation is applied. This creates a lot of special casing in this unit test but
  760. // does not expand its test coverage.
  761. if ([self gcIsEager]) return;
  762. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"sum" : @0})];
  763. FSTAssertContains(Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations));
  764. FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations));
  765. [self acknowledgeMutationWithVersion:1];
  766. FSTAssertContains(Doc("foo/bar", 1, Map("sum", 0), DocumentState::kCommittedMutations));
  767. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 0), DocumentState::kCommittedMutations));
  768. [self writeMutation:FSTTestTransformMutation(
  769. @"foo/bar", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:1]})];
  770. FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  771. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  772. [self acknowledgeMutationWithVersion:2 transformResult:@1];
  773. FSTAssertContains(Doc("foo/bar", 2, Map("sum", 1), DocumentState::kCommittedMutations));
  774. FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 1), DocumentState::kCommittedMutations));
  775. [self writeMutation:FSTTestTransformMutation(
  776. @"foo/bar", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:2]})];
  777. FSTAssertContains(Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations));
  778. FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations));
  779. }
  780. - (void)testHandlesSetMutationThenTransformMutationThenRemoteEventThenTransformMutation {
  781. if ([self isTestBaseClass]) return;
  782. core::Query query = Query("foo");
  783. [self allocateQuery:query];
  784. FSTAssertTargetID(2);
  785. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"sum" : @0})];
  786. FSTAssertContains(Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations));
  787. FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations));
  788. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 1, Map("sum", 0)), {2})];
  789. [self acknowledgeMutationWithVersion:1];
  790. FSTAssertContains(Doc("foo/bar", 1, Map("sum", 0)));
  791. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 0)));
  792. [self writeMutation:FSTTestTransformMutation(
  793. @"foo/bar", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:1]})];
  794. FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  795. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  796. // The value in this remote event gets ignored since we still have a pending transform mutation.
  797. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(Doc("foo/bar", 2, Map("sum", 0)), {2}, {})];
  798. FSTAssertContains(Doc("foo/bar", 2, Map("sum", 1), DocumentState::kLocalMutations));
  799. FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 1), DocumentState::kLocalMutations));
  800. // Add another increment. Note that we still compute the increment based on the local value.
  801. [self writeMutation:FSTTestTransformMutation(
  802. @"foo/bar", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:2]})];
  803. FSTAssertContains(Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations));
  804. FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations));
  805. [self acknowledgeMutationWithVersion:3 transformResult:@1];
  806. FSTAssertContains(Doc("foo/bar", 3, Map("sum", 3), DocumentState::kLocalMutations));
  807. FSTAssertChanged(Doc("foo/bar", 3, Map("sum", 3), DocumentState::kLocalMutations));
  808. [self acknowledgeMutationWithVersion:4 transformResult:@1339];
  809. FSTAssertContains(Doc("foo/bar", 4, Map("sum", 1339), DocumentState::kCommittedMutations));
  810. FSTAssertChanged(Doc("foo/bar", 4, Map("sum", 1339), DocumentState::kCommittedMutations));
  811. }
  812. - (void)testHoldsBackOnlyNonIdempotentTransforms {
  813. if ([self isTestBaseClass]) return;
  814. core::Query query = Query("foo");
  815. [self allocateQuery:query];
  816. FSTAssertTargetID(2);
  817. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"sum" : @0, @"array_union" : @[]})];
  818. FSTAssertChanged(
  819. Doc("foo/bar", 0, Map("sum", 0, "array_union", Array()), DocumentState::kLocalMutations));
  820. [self acknowledgeMutationWithVersion:1];
  821. FSTAssertChanged(
  822. Doc("foo/bar", 1, Map("sum", 0, "array_union", Array()), DocumentState::kCommittedMutations));
  823. [self applyRemoteEvent:FSTTestAddedRemoteEvent(
  824. Doc("foo/bar", 1, Map("sum", 0, "array_union", Array())), {2})];
  825. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 0, "array_union", Array())));
  826. [self writeMutations:{
  827. FSTTestTransformMutation(@"foo/bar",
  828. @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:1]}),
  829. FSTTestTransformMutation(
  830. @"foo/bar",
  831. @{@"array_union" : [FIRFieldValue fieldValueForArrayUnion:@[ @"foo" ]]})
  832. }];
  833. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1, "array_union", Array("foo")),
  834. DocumentState::kLocalMutations));
  835. // The sum transform is not idempotent and the backend's updated value is ignored. The
  836. // ArrayUnion transform is recomputed and includes the backend value.
  837. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  838. Doc("foo/bar", 2, Map("sum", 1337, "array_union", Array("bar"))), {2},
  839. {})];
  840. FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 1, "array_union", Array("bar", "foo")),
  841. DocumentState::kLocalMutations));
  842. }
  843. - (void)testHandlesMergeMutationWithTransformThenRemoteEvent {
  844. if ([self isTestBaseClass]) return;
  845. core::Query query = Query("foo");
  846. [self allocateQuery:query];
  847. FSTAssertTargetID(2);
  848. [self writeMutations:{
  849. FSTTestPatchMutation("foo/bar", @{}, {firebase::firestore::testutil::Field("sum")}),
  850. FSTTestTransformMutation(@"foo/bar",
  851. @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:1]})
  852. }];
  853. FSTAssertContains(Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations));
  854. FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations));
  855. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 1, Map("sum", 1337)), {2})];
  856. FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  857. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  858. }
  859. - (void)testHandlesPatchMutationWithTransformThenRemoteEvent {
  860. if ([self isTestBaseClass]) return;
  861. core::Query query = Query("foo");
  862. [self allocateQuery:query];
  863. FSTAssertTargetID(2);
  864. [self writeMutations:{
  865. FSTTestPatchMutation("foo/bar", @{}, {}),
  866. FSTTestTransformMutation(@"foo/bar",
  867. @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:1]})
  868. }];
  869. FSTAssertNotContains("foo/bar");
  870. FSTAssertChanged(DeletedDoc("foo/bar"));
  871. // Note: This test reflects the current behavior, but it may be preferable to replay the
  872. // mutation once we receive the first value from the remote event.
  873. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 1, Map("sum", 1337)), {2})];
  874. FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  875. FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations));
  876. }
  877. - (void)testGetHighestUnacknowledgeBatchId {
  878. if ([self isTestBaseClass]) return;
  879. XCTAssertEqual(-1, [self.localStore getHighestUnacknowledgedBatchId]);
  880. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"abc" : @123})];
  881. XCTAssertEqual(1, [self.localStore getHighestUnacknowledgedBatchId]);
  882. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"abc" : @321}, {})];
  883. XCTAssertEqual(2, [self.localStore getHighestUnacknowledgedBatchId]);
  884. [self acknowledgeMutationWithVersion:1];
  885. XCTAssertEqual(2, [self.localStore getHighestUnacknowledgedBatchId]);
  886. [self rejectMutation];
  887. XCTAssertEqual(-1, [self.localStore getHighestUnacknowledgedBatchId]);
  888. }
  889. - (void)testOnlyPersistsUpdatesForDocumentsWhenVersionChanges {
  890. if ([self isTestBaseClass]) return;
  891. core::Query query = Query("foo");
  892. [self allocateQuery:query];
  893. FSTAssertTargetID(2);
  894. [self applyRemoteEvent:FSTTestAddedRemoteEvent(Doc("foo/bar", 1, Map("val", "old")), {2})];
  895. FSTAssertContains(Doc("foo/bar", 1, Map("val", "old")));
  896. FSTAssertChanged(Doc("foo/bar", 1, Map("val", "old")));
  897. [self applyRemoteEvent:FSTTestAddedRemoteEvent({Doc("foo/bar", 1, Map("val", "new")),
  898. Doc("foo/baz", 2, Map("val", "new"))},
  899. {2})];
  900. // The update to foo/bar is ignored.
  901. FSTAssertContains(Doc("foo/bar", 1, Map("val", "old")));
  902. FSTAssertContains(Doc("foo/baz", 2, Map("val", "new")));
  903. FSTAssertChanged(Doc("foo/baz", 2, Map("val", "new")));
  904. }
  905. @end
  906. NS_ASSUME_NONNULL_END