FSTLocalStoreTests.mm 41 KB

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