FSTLocalStoreTests.mm 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  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/FSTEagerGarbageCollector.h"
  21. #import "Firestore/Source/Local/FSTLocalWriteResult.h"
  22. #import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
  23. #import "Firestore/Source/Local/FSTPersistence.h"
  24. #import "Firestore/Source/Local/FSTQueryData.h"
  25. #import "Firestore/Source/Model/FSTDocument.h"
  26. #import "Firestore/Source/Model/FSTDocumentKey.h"
  27. #import "Firestore/Source/Model/FSTDocumentSet.h"
  28. #import "Firestore/Source/Model/FSTMutation.h"
  29. #import "Firestore/Source/Model/FSTMutationBatch.h"
  30. #import "Firestore/Source/Remote/FSTRemoteEvent.h"
  31. #import "Firestore/Source/Remote/FSTWatchChange.h"
  32. #import "Firestore/Source/Util/FSTClasses.h"
  33. #import "Firestore/Example/Tests/Local/FSTLocalStoreTests.h"
  34. #import "Firestore/Example/Tests/Remote/FSTWatchChange+Testing.h"
  35. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  36. #import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedDictionary+Testing.h"
  37. #import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.h"
  38. #include "Firestore/core/src/firebase/firestore/auth/user.h"
  39. using firebase::firestore::auth::User;
  40. NS_ASSUME_NONNULL_BEGIN
  41. /** Creates a document version dictionary mapping the document in @a mutation to @a version. */
  42. FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
  43. FSTTestSnapshotVersion version) {
  44. FSTDocumentVersionDictionary *result = [FSTDocumentVersionDictionary documentVersionDictionary];
  45. result = [result dictionaryBySettingObject:FSTTestVersion(version) forKey:mutation.key];
  46. return result;
  47. }
  48. @interface FSTLocalStoreTests ()
  49. @property(nonatomic, strong, readwrite) id<FSTPersistence> localStorePersistence;
  50. @property(nonatomic, strong, readwrite) FSTLocalStore *localStore;
  51. @property(nonatomic, strong, readonly) NSMutableArray<FSTMutationBatch *> *batches;
  52. @property(nonatomic, strong, readwrite, nullable) FSTMaybeDocumentDictionary *lastChanges;
  53. @property(nonatomic, assign, readwrite) FSTTargetID lastTargetID;
  54. @end
  55. @implementation FSTLocalStoreTests
  56. - (void)setUp {
  57. [super setUp];
  58. if ([self isTestBaseClass]) {
  59. return;
  60. }
  61. id<FSTPersistence> persistence = [self persistence];
  62. self.localStorePersistence = persistence;
  63. id<FSTGarbageCollector> garbageCollector = [[FSTEagerGarbageCollector alloc] init];
  64. self.localStore = [[FSTLocalStore alloc] initWithPersistence:persistence
  65. garbageCollector:garbageCollector
  66. initialUser:User::Unauthenticated()];
  67. [self.localStore start];
  68. _batches = [NSMutableArray array];
  69. _lastChanges = nil;
  70. _lastTargetID = 0;
  71. }
  72. - (void)tearDown {
  73. [self.localStore shutdown];
  74. [self.localStorePersistence shutdown];
  75. [super tearDown];
  76. }
  77. - (id<FSTPersistence>)persistence {
  78. @throw FSTAbstractMethodException(); // NOLINT
  79. }
  80. /**
  81. * Xcode will run tests from any class that extends XCTestCase, but this doesn't work for
  82. * FSTLocalStoreTests since it is incomplete without the implementations supplied by its
  83. * subclasses.
  84. */
  85. - (BOOL)isTestBaseClass {
  86. return [self class] == [FSTLocalStoreTests class];
  87. }
  88. /** Restarts the local store using the FSTNoOpGarbageCollector instead of the default. */
  89. - (void)restartWithNoopGarbageCollector {
  90. [self.localStore shutdown];
  91. id<FSTGarbageCollector> garbageCollector = [[FSTNoOpGarbageCollector alloc] init];
  92. self.localStore = [[FSTLocalStore alloc] initWithPersistence:self.localStorePersistence
  93. garbageCollector:garbageCollector
  94. initialUser:User::Unauthenticated()];
  95. [self.localStore start];
  96. }
  97. - (void)writeMutation:(FSTMutation *)mutation {
  98. [self writeMutations:@[ mutation ]];
  99. }
  100. - (void)writeMutations:(NSArray<FSTMutation *> *)mutations {
  101. FSTLocalWriteResult *result = [self.localStore locallyWriteMutations:mutations];
  102. XCTAssertNotNil(result);
  103. [self.batches addObject:[[FSTMutationBatch alloc] initWithBatchID:result.batchID
  104. localWriteTime:[FIRTimestamp timestamp]
  105. mutations:mutations]];
  106. self.lastChanges = result.changes;
  107. }
  108. - (void)applyRemoteEvent:(FSTRemoteEvent *)event {
  109. self.lastChanges = [self.localStore applyRemoteEvent:event];
  110. }
  111. - (void)notifyLocalViewChanges:(FSTLocalViewChanges *)changes {
  112. [self.localStore notifyLocalViewChanges:@[ changes ]];
  113. }
  114. - (void)acknowledgeMutationWithVersion:(FSTTestSnapshotVersion)documentVersion {
  115. FSTMutationBatch *batch = [self.batches firstObject];
  116. [self.batches removeObjectAtIndex:0];
  117. XCTAssertEqual(batch.mutations.count, 1, @"Acknowledging more than one mutation not supported.");
  118. FSTSnapshotVersion *version = FSTTestVersion(documentVersion);
  119. FSTMutationResult *mutationResult =
  120. [[FSTMutationResult alloc] initWithVersion:version transformResults:nil];
  121. FSTMutationBatchResult *result = [FSTMutationBatchResult resultWithBatch:batch
  122. commitVersion:version
  123. mutationResults:@[ mutationResult ]
  124. streamToken:nil];
  125. self.lastChanges = [self.localStore acknowledgeBatchWithResult:result];
  126. }
  127. - (void)rejectMutation {
  128. FSTMutationBatch *batch = [self.batches firstObject];
  129. [self.batches removeObjectAtIndex:0];
  130. self.lastChanges = [self.localStore rejectBatchID:batch.batchID];
  131. }
  132. - (void)allocateQuery:(FSTQuery *)query {
  133. FSTQueryData *queryData = [self.localStore allocateQuery:query];
  134. self.lastTargetID = queryData.targetID;
  135. }
  136. - (void)collectGarbage {
  137. [self.localStore collectGarbage];
  138. }
  139. /** Asserts that the last target ID is the given number. */
  140. #define FSTAssertTargetID(targetID) \
  141. do { \
  142. XCTAssertEqual(self.lastTargetID, targetID); \
  143. } while (0)
  144. /** Asserts that a the lastChanges contain the docs in the given array. */
  145. #define FSTAssertChanged(documents) \
  146. XCTAssertNotNil(self.lastChanges); \
  147. do { \
  148. FSTMaybeDocumentDictionary *actual = self.lastChanges; \
  149. NSArray<FSTMaybeDocument *> *expected = (documents); \
  150. XCTAssertEqual(actual.count, expected.count); \
  151. NSEnumerator<FSTMaybeDocument *> *enumerator = expected.objectEnumerator; \
  152. [actual enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey * key, FSTMaybeDocument * value, \
  153. BOOL * stop) { \
  154. XCTAssertEqualObjects(value, [enumerator nextObject]); \
  155. }]; \
  156. self.lastChanges = nil; \
  157. } while (0)
  158. /** Asserts that the given keys were removed. */
  159. #define FSTAssertRemoved(keyPaths) \
  160. XCTAssertNotNil(self.lastChanges); \
  161. do { \
  162. FSTMaybeDocumentDictionary *actual = self.lastChanges; \
  163. XCTAssertEqual(actual.count, keyPaths.count); \
  164. NSEnumerator<NSString *> *keyPathEnumerator = keyPaths.objectEnumerator; \
  165. [actual enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey * actualKey, \
  166. FSTMaybeDocument * value, BOOL * stop) { \
  167. FSTDocumentKey *expectedKey = FSTTestDocKey([keyPathEnumerator nextObject]); \
  168. XCTAssertEqualObjects(actualKey, expectedKey); \
  169. XCTAssertTrue([value isKindOfClass:[FSTDeletedDocument class]]); \
  170. }]; \
  171. self.lastChanges = nil; \
  172. } while (0)
  173. /** Asserts that the given local store contains the given document. */
  174. #define FSTAssertContains(document) \
  175. do { \
  176. FSTMaybeDocument *expected = (document); \
  177. FSTMaybeDocument *actual = [self.localStore readDocument:expected.key]; \
  178. XCTAssertEqualObjects(actual, expected); \
  179. } while (0)
  180. /** Asserts that the given local store does not contain the given document. */
  181. #define FSTAssertNotContains(keyPathString) \
  182. do { \
  183. FSTDocumentKey *key = FSTTestDocKey(keyPathString); \
  184. FSTMaybeDocument *actual = [self.localStore readDocument:key]; \
  185. XCTAssertNil(actual); \
  186. } while (0)
  187. - (void)testMutationBatchKeys {
  188. if ([self isTestBaseClass]) return;
  189. FSTMutation *set1 = FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"});
  190. FSTMutation *set2 = FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"});
  191. FSTMutationBatch *batch = [[FSTMutationBatch alloc] initWithBatchID:1
  192. localWriteTime:[FIRTimestamp timestamp]
  193. mutations:@[ set1, set2 ]];
  194. FSTDocumentKeySet *keys = [batch keys];
  195. XCTAssertEqual(keys.count, 2);
  196. }
  197. - (void)testHandlesSetMutation {
  198. if ([self isTestBaseClass]) return;
  199. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  200. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  201. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  202. [self acknowledgeMutationWithVersion:0];
  203. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
  204. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO));
  205. }
  206. - (void)testHandlesSetMutationThenDocument {
  207. if ([self isTestBaseClass]) return;
  208. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  209. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  210. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  211. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  212. FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  213. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
  214. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES));
  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. [self allocateQuery:query];
  221. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  222. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  223. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  224. // The last seen version is zero, so this ack must be held.
  225. [self acknowledgeMutationWithVersion:1];
  226. FSTAssertChanged(@[]);
  227. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  228. [self writeMutation:FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"})];
  229. FSTAssertChanged(@[ FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES) ]);
  230. FSTAssertContains(FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES));
  231. [self rejectMutation];
  232. FSTAssertRemoved(@[ @"bar/baz" ]);
  233. FSTAssertNotContains(@"bar/baz");
  234. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  235. FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  236. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO) ]);
  237. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO));
  238. FSTAssertNotContains(@"bar/baz");
  239. }
  240. - (void)testHandlesDeletedDocumentThenSetMutationThenAck {
  241. if ([self isTestBaseClass]) return;
  242. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  243. FSTAssertRemoved(@[ @"foo/bar" ]);
  244. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 2));
  245. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  246. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  247. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  248. [self acknowledgeMutationWithVersion:3];
  249. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
  250. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO));
  251. }
  252. - (void)testHandlesSetMutationThenDeletedDocument {
  253. if ([self isTestBaseClass]) return;
  254. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  255. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  256. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  257. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  258. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  259. }
  260. - (void)testHandlesDocumentThenSetMutationThenAckThenDocument {
  261. if ([self isTestBaseClass]) return;
  262. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO),
  263. @[ @1 ], @[])];
  264. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO) ]);
  265. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO));
  266. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  267. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
  268. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES));
  269. [self acknowledgeMutationWithVersion:3];
  270. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO) ]);
  271. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO));
  272. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  273. FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  274. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
  275. FSTAssertContains(FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO));
  276. }
  277. - (void)testHandlesPatchWithoutPriorDocument {
  278. if ([self isTestBaseClass]) return;
  279. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  280. FSTAssertRemoved(@[ @"foo/bar" ]);
  281. FSTAssertNotContains(@"foo/bar");
  282. [self acknowledgeMutationWithVersion:1];
  283. FSTAssertRemoved(@[ @"foo/bar" ]);
  284. FSTAssertNotContains(@"foo/bar");
  285. }
  286. - (void)testHandlesPatchMutationThenDocumentThenAck {
  287. if ([self isTestBaseClass]) return;
  288. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  289. FSTAssertRemoved(@[ @"foo/bar" ]);
  290. FSTAssertNotContains(@"foo/bar");
  291. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  292. @[ @1 ], @[])];
  293. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES) ]);
  294. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES));
  295. [self acknowledgeMutationWithVersion:2];
  296. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO) ]);
  297. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO));
  298. }
  299. - (void)testHandlesPatchMutationThenAckThenDocument {
  300. if ([self isTestBaseClass]) return;
  301. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  302. FSTAssertRemoved(@[ @"foo/bar" ]);
  303. FSTAssertNotContains(@"foo/bar");
  304. [self acknowledgeMutationWithVersion:1];
  305. FSTAssertRemoved(@[ @"foo/bar" ]);
  306. FSTAssertNotContains(@"foo/bar");
  307. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  308. @[ @1 ], @[])];
  309. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
  310. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
  311. }
  312. - (void)testHandlesDeleteMutationThenAck {
  313. if ([self isTestBaseClass]) return;
  314. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  315. FSTAssertRemoved(@[ @"foo/bar" ]);
  316. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  317. [self acknowledgeMutationWithVersion:1];
  318. FSTAssertRemoved(@[ @"foo/bar" ]);
  319. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  320. }
  321. - (void)testHandlesDocumentThenDeleteMutationThenAck {
  322. if ([self isTestBaseClass]) return;
  323. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  324. @[ @1 ], @[])];
  325. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
  326. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
  327. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  328. FSTAssertRemoved(@[ @"foo/bar" ]);
  329. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  330. [self acknowledgeMutationWithVersion:2];
  331. FSTAssertRemoved(@[ @"foo/bar" ]);
  332. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  333. }
  334. - (void)testHandlesDeleteMutationThenDocumentThenAck {
  335. if ([self isTestBaseClass]) return;
  336. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  337. FSTAssertRemoved(@[ @"foo/bar" ]);
  338. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  339. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  340. @[ @1 ], @[])];
  341. FSTAssertRemoved(@[ @"foo/bar" ]);
  342. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  343. [self acknowledgeMutationWithVersion:2];
  344. FSTAssertRemoved(@[ @"foo/bar" ]);
  345. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  346. }
  347. - (void)testHandlesDocumentThenDeletedDocumentThenDocument {
  348. if ([self isTestBaseClass]) return;
  349. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  350. @[ @1 ], @[])];
  351. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
  352. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
  353. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  354. FSTAssertRemoved(@[ @"foo/bar" ]);
  355. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 2));
  356. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  357. FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  358. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
  359. FSTAssertContains(FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO));
  360. }
  361. - (void)testHandlesSetMutationThenPatchMutationThenDocumentThenAckThenAck {
  362. if ([self isTestBaseClass]) return;
  363. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
  364. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, YES) ]);
  365. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, YES));
  366. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  367. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  368. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  369. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  370. @[ @1 ], @[])];
  371. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
  372. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES));
  373. [self acknowledgeMutationWithVersion:2]; // delete mutation
  374. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
  375. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES));
  376. [self acknowledgeMutationWithVersion:3]; // patch mutation
  377. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO) ]);
  378. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
  379. }
  380. - (void)testHandlesSetMutationAndPatchMutationTogether {
  381. if ([self isTestBaseClass]) return;
  382. [self writeMutations:@[
  383. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
  384. FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})
  385. ]];
  386. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  387. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  388. }
  389. - (void)testHandlesSetMutationThenPatchMutationThenReject {
  390. if ([self isTestBaseClass]) return;
  391. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
  392. [self acknowledgeMutationWithVersion:1];
  393. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO));
  394. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  395. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  396. [self rejectMutation];
  397. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO) ]);
  398. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO));
  399. }
  400. - (void)testHandlesSetMutationsAndPatchMutationOfJustOneTogether {
  401. if ([self isTestBaseClass]) return;
  402. [self writeMutations:@[
  403. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
  404. FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"}),
  405. FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})
  406. ]];
  407. FSTAssertChanged((@[
  408. FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES),
  409. FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES)
  410. ]));
  411. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  412. FSTAssertContains(FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES));
  413. }
  414. - (void)testHandlesDeleteMutationThenPatchMutationThenAckThenAck {
  415. if ([self isTestBaseClass]) return;
  416. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  417. FSTAssertRemoved(@[ @"foo/bar" ]);
  418. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  419. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  420. FSTAssertRemoved(@[ @"foo/bar" ]);
  421. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  422. [self acknowledgeMutationWithVersion:2]; // delete mutation
  423. FSTAssertRemoved(@[ @"foo/bar" ]);
  424. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  425. [self acknowledgeMutationWithVersion:3]; // patch mutation
  426. FSTAssertRemoved(@[ @"foo/bar" ]);
  427. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  428. }
  429. - (void)testCollectsGarbageAfterChangeBatchWithNoTargetIDs {
  430. if ([self isTestBaseClass]) return;
  431. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  432. FSTAssertRemoved(@[ @"foo/bar" ]);
  433. [self collectGarbage];
  434. FSTAssertNotContains(@"foo/bar");
  435. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO),
  436. @[ @1 ], @[])];
  437. [self collectGarbage];
  438. FSTAssertNotContains(@"foo/bar");
  439. }
  440. - (void)testCollectsGarbageAfterChangeBatch {
  441. if ([self isTestBaseClass]) return;
  442. FSTQuery *query = FSTTestQuery("foo");
  443. [self allocateQuery:query];
  444. FSTAssertTargetID(2);
  445. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO),
  446. @[ @2 ], @[])];
  447. [self collectGarbage];
  448. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO));
  449. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"baz"}, NO),
  450. @[], @[ @2 ])];
  451. [self collectGarbage];
  452. FSTAssertNotContains(@"foo/bar");
  453. }
  454. - (void)testCollectsGarbageAfterAcknowledgedMutation {
  455. if ([self isTestBaseClass]) return;
  456. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO),
  457. @[ @1 ], @[])];
  458. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  459. [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
  460. [self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
  461. [self collectGarbage];
  462. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  463. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  464. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  465. [self acknowledgeMutationWithVersion:3];
  466. [self collectGarbage];
  467. FSTAssertNotContains(@"foo/bar");
  468. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  469. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  470. [self acknowledgeMutationWithVersion:4];
  471. [self collectGarbage];
  472. FSTAssertNotContains(@"foo/bar");
  473. FSTAssertNotContains(@"foo/bah");
  474. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  475. [self acknowledgeMutationWithVersion:5];
  476. [self collectGarbage];
  477. FSTAssertNotContains(@"foo/bar");
  478. FSTAssertNotContains(@"foo/bah");
  479. FSTAssertNotContains(@"foo/baz");
  480. }
  481. - (void)testCollectsGarbageAfterRejectedMutation {
  482. if ([self isTestBaseClass]) return;
  483. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO),
  484. @[ @1 ], @[])];
  485. [self writeMutation:FSTTestPatchMutation("foo/bar", @{@"foo" : @"bar"}, {})];
  486. [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
  487. [self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
  488. [self collectGarbage];
  489. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  490. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  491. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  492. [self rejectMutation]; // patch mutation
  493. [self collectGarbage];
  494. FSTAssertNotContains(@"foo/bar");
  495. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  496. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  497. [self rejectMutation]; // set mutation
  498. [self collectGarbage];
  499. FSTAssertNotContains(@"foo/bar");
  500. FSTAssertNotContains(@"foo/bah");
  501. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  502. [self rejectMutation]; // delete mutation
  503. [self collectGarbage];
  504. FSTAssertNotContains(@"foo/bar");
  505. FSTAssertNotContains(@"foo/bah");
  506. FSTAssertNotContains(@"foo/baz");
  507. }
  508. - (void)testPinsDocumentsInTheLocalView {
  509. if ([self isTestBaseClass]) return;
  510. FSTQuery *query = FSTTestQuery("foo");
  511. [self allocateQuery:query];
  512. FSTAssertTargetID(2);
  513. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO),
  514. @[ @2 ], @[])];
  515. [self writeMutation:FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"})];
  516. [self collectGarbage];
  517. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
  518. FSTAssertContains(FSTTestDoc(@"foo/baz", 0, @{@"foo" : @"baz"}, YES));
  519. [self notifyLocalViewChanges:FSTTestViewChanges(query, @[ @"foo/bar", @"foo/baz" ], @[])];
  520. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO),
  521. @[], @[ @2 ])];
  522. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 2, @{@"foo" : @"baz"}, NO),
  523. @[ @1 ], @[])];
  524. [self acknowledgeMutationWithVersion:2];
  525. [self collectGarbage];
  526. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
  527. FSTAssertContains(FSTTestDoc(@"foo/baz", 2, @{@"foo" : @"baz"}, NO));
  528. [self notifyLocalViewChanges:FSTTestViewChanges(query, @[], @[ @"foo/bar", @"foo/baz" ])];
  529. [self collectGarbage];
  530. FSTAssertNotContains(@"foo/bar");
  531. FSTAssertNotContains(@"foo/baz");
  532. }
  533. - (void)testThrowsAwayDocumentsWithUnknownTargetIDsImmediately {
  534. if ([self isTestBaseClass]) return;
  535. FSTTargetID targetID = 321;
  536. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{}, NO),
  537. @[ @(targetID) ], @[])];
  538. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{}, NO));
  539. [self collectGarbage];
  540. FSTAssertNotContains(@"foo/bar");
  541. }
  542. - (void)testCanExecuteDocumentQueries {
  543. if ([self isTestBaseClass]) return;
  544. [self.localStore locallyWriteMutations:@[
  545. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"}),
  546. FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
  547. FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"})
  548. ]];
  549. FSTQuery *query = FSTTestQuery("foo/bar");
  550. FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
  551. XCTAssertEqualObjects([docs values], @[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  552. }
  553. - (void)testCanExecuteCollectionQueries {
  554. if ([self isTestBaseClass]) return;
  555. [self.localStore locallyWriteMutations:@[
  556. FSTTestSetMutation(@"fo/bar", @{@"fo" : @"bar"}),
  557. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"}),
  558. FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
  559. FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"}),
  560. FSTTestSetMutation(@"fooo/blah", @{@"fooo" : @"blah"})
  561. ]];
  562. FSTQuery *query = FSTTestQuery("foo");
  563. FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
  564. XCTAssertEqualObjects([docs values], (@[
  565. FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES),
  566. FSTTestDoc(@"foo/baz", 0, @{@"foo" : @"baz"}, YES)
  567. ]));
  568. }
  569. - (void)testCanExecuteMixedCollectionQueries {
  570. if ([self isTestBaseClass]) return;
  571. FSTQuery *query = FSTTestQuery("foo");
  572. [self allocateQuery:query];
  573. FSTAssertTargetID(2);
  574. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
  575. @[ @2 ], @[])];
  576. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
  577. @[ @2 ], @[])];
  578. [self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
  579. FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
  580. XCTAssertEqualObjects([docs values], (@[
  581. FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
  582. FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
  583. FSTTestDoc(@"foo/bonk", 0, @{@"a" : @"b"}, YES)
  584. ]));
  585. }
  586. - (void)testPersistsResumeTokens {
  587. if ([self isTestBaseClass]) return;
  588. // This test only works in the absence of the FSTEagerGarbageCollector.
  589. [self restartWithNoopGarbageCollector];
  590. FSTQuery *query = FSTTestQuery("foo/bar");
  591. FSTQueryData *queryData = [self.localStore allocateQuery:query];
  592. FSTBoxedTargetID *targetID = @(queryData.targetID);
  593. NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1000);
  594. FSTWatchChange *watchChange =
  595. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  596. targetIDs:@[ targetID ]
  597. resumeToken:resumeToken];
  598. NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *listens =
  599. [NSMutableDictionary dictionary];
  600. listens[targetID] = queryData;
  601. NSMutableDictionary<FSTBoxedTargetID *, NSNumber *> *pendingResponses =
  602. [NSMutableDictionary dictionary];
  603. FSTWatchChangeAggregator *aggregator =
  604. [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:FSTTestVersion(1000)
  605. listenTargets:listens
  606. pendingTargetResponses:pendingResponses];
  607. [aggregator addWatchChanges:@[ watchChange ]];
  608. FSTRemoteEvent *remoteEvent = [aggregator remoteEvent];
  609. [self applyRemoteEvent:remoteEvent];
  610. // Stop listening so that the query should become inactive (but persistent)
  611. [self.localStore releaseQuery:query];
  612. // Should come back with the same resume token
  613. FSTQueryData *queryData2 = [self.localStore allocateQuery:query];
  614. XCTAssertEqualObjects(queryData2.resumeToken, resumeToken);
  615. }
  616. - (void)testRemoteDocumentKeysForTarget {
  617. if ([self isTestBaseClass]) return;
  618. [self restartWithNoopGarbageCollector];
  619. FSTQuery *query = FSTTestQuery("foo");
  620. [self allocateQuery:query];
  621. FSTAssertTargetID(2);
  622. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
  623. @[ @2 ], @[])];
  624. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
  625. @[ @2 ], @[])];
  626. [self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
  627. FSTDocumentKeySet *keys = [self.localStore remoteDocumentKeysForTarget:2];
  628. FSTAssertEqualSets(keys, (@[ FSTTestDocKey(@"foo/bar"), FSTTestDocKey(@"foo/baz") ]));
  629. [self restartWithNoopGarbageCollector];
  630. keys = [self.localStore remoteDocumentKeysForTarget:2];
  631. FSTAssertEqualSets(keys, (@[ FSTTestDocKey(@"foo/bar"), FSTTestDocKey(@"foo/baz") ]));
  632. }
  633. @end
  634. NS_ASSUME_NONNULL_END