FSTLocalStoreTests.mm 50 KB

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