FSTLocalStoreTests.mm 40 KB

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