FSTLocalStoreTests.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  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 <XCTest/XCTest.h>
  18. #import "Firestore/Source/Auth/FSTUser.h"
  19. #import "Firestore/Source/Core/FSTQuery.h"
  20. #import "Firestore/Source/Core/FSTTimestamp.h"
  21. #import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
  22. #import "Firestore/Source/Local/FSTLocalWriteResult.h"
  23. #import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
  24. #import "Firestore/Source/Local/FSTPersistence.h"
  25. #import "Firestore/Source/Local/FSTQueryData.h"
  26. #import "Firestore/Source/Model/FSTDocument.h"
  27. #import "Firestore/Source/Model/FSTDocumentKey.h"
  28. #import "Firestore/Source/Model/FSTDocumentSet.h"
  29. #import "Firestore/Source/Model/FSTMutation.h"
  30. #import "Firestore/Source/Model/FSTMutationBatch.h"
  31. #import "Firestore/Source/Model/FSTPath.h"
  32. #import "Firestore/Source/Remote/FSTRemoteEvent.h"
  33. #import "Firestore/Source/Remote/FSTWatchChange.h"
  34. #import "Firestore/Source/Util/FSTClasses.h"
  35. #import "Firestore/Example/Tests/Local/FSTLocalStoreTests.h"
  36. #import "Firestore/Example/Tests/Remote/FSTWatchChange+Testing.h"
  37. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  38. #import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedDictionary+Testing.h"
  39. #import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.h"
  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:[FSTUser unauthenticatedUser]];
  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:[FSTUser unauthenticatedUser]];
  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:[FSTTimestamp 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 = \
  168. [FSTDocumentKey keyWithPathString:[keyPathEnumerator nextObject]]; \
  169. XCTAssertEqualObjects(actualKey, expectedKey); \
  170. XCTAssertTrue([value isKindOfClass:[FSTDeletedDocument class]]); \
  171. }]; \
  172. self.lastChanges = nil; \
  173. } while (0)
  174. /** Asserts that the given local store contains the given document. */
  175. #define FSTAssertContains(document) \
  176. do { \
  177. FSTMaybeDocument *expected = (document); \
  178. FSTMaybeDocument *actual = [self.localStore readDocument:expected.key]; \
  179. XCTAssertEqualObjects(actual, expected); \
  180. } while (0)
  181. /** Asserts that the given local store does not contain the given document. */
  182. #define FSTAssertNotContains(keyPathString) \
  183. do { \
  184. FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:keyPathString]; \
  185. FSTMaybeDocument *actual = [self.localStore readDocument:key]; \
  186. XCTAssertNil(actual); \
  187. } while (0)
  188. - (void)testMutationBatchKeys {
  189. if ([self isTestBaseClass]) return;
  190. FSTMutation *set1 = FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"});
  191. FSTMutation *set2 = FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"});
  192. FSTMutationBatch *batch = [[FSTMutationBatch alloc] initWithBatchID:1
  193. localWriteTime:[FSTTimestamp timestamp]
  194. mutations:@[ set1, set2 ]];
  195. FSTDocumentKeySet *keys = [batch keys];
  196. XCTAssertEqual(keys.count, 2);
  197. }
  198. - (void)testHandlesSetMutation {
  199. if ([self isTestBaseClass]) return;
  200. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  201. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  202. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  203. [self acknowledgeMutationWithVersion:0];
  204. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
  205. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO));
  206. }
  207. - (void)testHandlesSetMutationThenDocument {
  208. if ([self isTestBaseClass]) return;
  209. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  210. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  211. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  212. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  213. FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  214. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
  215. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES));
  216. }
  217. - (void)testHandlesAckThenRejectThenRemoteEvent {
  218. if ([self isTestBaseClass]) return;
  219. // Start a query that requires acks to be held.
  220. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
  221. [self allocateQuery:query];
  222. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  223. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  224. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  225. // The last seen version is zero, so this ack must be held.
  226. [self acknowledgeMutationWithVersion:1];
  227. FSTAssertChanged(@[]);
  228. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  229. [self writeMutation:FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"})];
  230. FSTAssertChanged(@[ FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES) ]);
  231. FSTAssertContains(FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES));
  232. [self rejectMutation];
  233. FSTAssertRemoved(@[ @"bar/baz" ]);
  234. FSTAssertNotContains(@"bar/baz");
  235. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  236. FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  237. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO) ]);
  238. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"changed"}, NO));
  239. FSTAssertNotContains(@"bar/baz");
  240. }
  241. - (void)testHandlesDeletedDocumentThenSetMutationThenAck {
  242. if ([self isTestBaseClass]) return;
  243. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  244. FSTAssertRemoved(@[ @"foo/bar" ]);
  245. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 2));
  246. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  247. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  248. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  249. [self acknowledgeMutationWithVersion:3];
  250. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO) ]);
  251. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, NO));
  252. }
  253. - (void)testHandlesSetMutationThenDeletedDocument {
  254. if ([self isTestBaseClass]) return;
  255. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  256. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  257. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  258. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  259. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  260. }
  261. - (void)testHandlesDocumentThenSetMutationThenAckThenDocument {
  262. if ([self isTestBaseClass]) return;
  263. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO),
  264. @[ @1 ], @[])];
  265. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO) ]);
  266. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"it" : @"base"}, NO));
  267. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
  268. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES) ]);
  269. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, YES));
  270. [self acknowledgeMutationWithVersion:3];
  271. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO) ]);
  272. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO));
  273. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  274. FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  275. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
  276. FSTAssertContains(FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO));
  277. }
  278. - (void)testHandlesPatchWithoutPriorDocument {
  279. if ([self isTestBaseClass]) return;
  280. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  281. FSTAssertRemoved(@[ @"foo/bar" ]);
  282. FSTAssertNotContains(@"foo/bar");
  283. [self acknowledgeMutationWithVersion:1];
  284. FSTAssertRemoved(@[ @"foo/bar" ]);
  285. FSTAssertNotContains(@"foo/bar");
  286. }
  287. - (void)testHandlesPatchMutationThenDocumentThenAck {
  288. if ([self isTestBaseClass]) return;
  289. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  290. FSTAssertRemoved(@[ @"foo/bar" ]);
  291. FSTAssertNotContains(@"foo/bar");
  292. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  293. @[ @1 ], @[])];
  294. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES) ]);
  295. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, YES));
  296. [self acknowledgeMutationWithVersion:2];
  297. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO) ]);
  298. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar", @"it" : @"base"}, NO));
  299. }
  300. - (void)testHandlesPatchMutationThenAckThenDocument {
  301. if ([self isTestBaseClass]) return;
  302. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  303. FSTAssertRemoved(@[ @"foo/bar" ]);
  304. FSTAssertNotContains(@"foo/bar");
  305. [self acknowledgeMutationWithVersion:1];
  306. FSTAssertRemoved(@[ @"foo/bar" ]);
  307. FSTAssertNotContains(@"foo/bar");
  308. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  309. @[ @1 ], @[])];
  310. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
  311. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
  312. }
  313. - (void)testHandlesDeleteMutationThenAck {
  314. if ([self isTestBaseClass]) return;
  315. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  316. FSTAssertRemoved(@[ @"foo/bar" ]);
  317. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  318. [self acknowledgeMutationWithVersion:1];
  319. FSTAssertRemoved(@[ @"foo/bar" ]);
  320. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  321. }
  322. - (void)testHandlesDocumentThenDeleteMutationThenAck {
  323. if ([self isTestBaseClass]) return;
  324. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  325. @[ @1 ], @[])];
  326. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
  327. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
  328. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  329. FSTAssertRemoved(@[ @"foo/bar" ]);
  330. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  331. [self acknowledgeMutationWithVersion:2];
  332. FSTAssertRemoved(@[ @"foo/bar" ]);
  333. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  334. }
  335. - (void)testHandlesDeleteMutationThenDocumentThenAck {
  336. if ([self isTestBaseClass]) return;
  337. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  338. FSTAssertRemoved(@[ @"foo/bar" ]);
  339. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  340. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  341. @[ @1 ], @[])];
  342. FSTAssertRemoved(@[ @"foo/bar" ]);
  343. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  344. [self acknowledgeMutationWithVersion:2];
  345. FSTAssertRemoved(@[ @"foo/bar" ]);
  346. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  347. }
  348. - (void)testHandlesDocumentThenDeletedDocumentThenDocument {
  349. if ([self isTestBaseClass]) return;
  350. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  351. @[ @1 ], @[])];
  352. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO) ]);
  353. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO));
  354. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  355. FSTAssertRemoved(@[ @"foo/bar" ]);
  356. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 2));
  357. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(
  358. FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO), @[ @1 ], @[])];
  359. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO) ]);
  360. FSTAssertContains(FSTTestDoc(@"foo/bar", 3, @{@"it" : @"changed"}, NO));
  361. }
  362. - (void)testHandlesSetMutationThenPatchMutationThenDocumentThenAckThenAck {
  363. if ([self isTestBaseClass]) return;
  364. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
  365. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, YES) ]);
  366. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, YES));
  367. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  368. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  369. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  370. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"it" : @"base"}, NO),
  371. @[ @1 ], @[])];
  372. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
  373. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES));
  374. [self acknowledgeMutationWithVersion:2]; // delete mutation
  375. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES) ]);
  376. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, YES));
  377. [self acknowledgeMutationWithVersion:3]; // patch mutation
  378. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO) ]);
  379. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
  380. }
  381. - (void)testHandlesSetMutationAndPatchMutationTogether {
  382. if ([self isTestBaseClass]) return;
  383. [self writeMutations:@[
  384. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
  385. FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)
  386. ]];
  387. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  388. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  389. }
  390. - (void)testHandlesSetMutationThenPatchMutationThenReject {
  391. if ([self isTestBaseClass]) return;
  392. [self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"})];
  393. [self acknowledgeMutationWithVersion:1];
  394. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO));
  395. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  396. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  397. [self rejectMutation];
  398. FSTAssertChanged(@[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO) ]);
  399. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO));
  400. }
  401. - (void)testHandlesSetMutationsAndPatchMutationOfJustOneTogether {
  402. if ([self isTestBaseClass]) return;
  403. [self writeMutations:@[
  404. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"old"}),
  405. FSTTestSetMutation(@"bar/baz", @{@"bar" : @"baz"}),
  406. FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)
  407. ]];
  408. FSTAssertChanged((@[
  409. FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES),
  410. FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES)
  411. ]));
  412. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  413. FSTAssertContains(FSTTestDoc(@"bar/baz", 0, @{@"bar" : @"baz"}, YES));
  414. }
  415. - (void)testHandlesDeleteMutationThenPatchMutationThenAckThenAck {
  416. if ([self isTestBaseClass]) return;
  417. [self writeMutation:FSTTestDeleteMutation(@"foo/bar")];
  418. FSTAssertRemoved(@[ @"foo/bar" ]);
  419. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  420. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  421. FSTAssertRemoved(@[ @"foo/bar" ]);
  422. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  423. [self acknowledgeMutationWithVersion:2]; // delete mutation
  424. FSTAssertRemoved(@[ @"foo/bar" ]);
  425. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  426. [self acknowledgeMutationWithVersion:3]; // patch mutation
  427. FSTAssertRemoved(@[ @"foo/bar" ]);
  428. FSTAssertContains(FSTTestDeletedDoc(@"foo/bar", 0));
  429. }
  430. - (void)testCollectsGarbageAfterChangeBatchWithNoTargetIDs {
  431. if ([self isTestBaseClass]) return;
  432. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDeletedDoc(@"foo/bar", 2), @[ @1 ], @[])];
  433. FSTAssertRemoved(@[ @"foo/bar" ]);
  434. [self collectGarbage];
  435. FSTAssertNotContains(@"foo/bar");
  436. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO),
  437. @[ @1 ], @[])];
  438. [self collectGarbage];
  439. FSTAssertNotContains(@"foo/bar");
  440. }
  441. - (void)testCollectsGarbageAfterChangeBatch {
  442. if ([self isTestBaseClass]) return;
  443. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
  444. [self allocateQuery:query];
  445. FSTAssertTargetID(2);
  446. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO),
  447. @[ @2 ], @[])];
  448. [self collectGarbage];
  449. FSTAssertContains(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"bar"}, NO));
  450. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 2, @{@"foo" : @"baz"}, NO),
  451. @[], @[ @2 ])];
  452. [self collectGarbage];
  453. FSTAssertNotContains(@"foo/bar");
  454. }
  455. - (void)testCollectsGarbageAfterAcknowledgedMutation {
  456. if ([self isTestBaseClass]) return;
  457. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO),
  458. @[ @1 ], @[])];
  459. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  460. [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
  461. [self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
  462. [self collectGarbage];
  463. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  464. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  465. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  466. [self acknowledgeMutationWithVersion:3];
  467. [self collectGarbage];
  468. FSTAssertNotContains(@"foo/bar");
  469. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  470. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  471. [self acknowledgeMutationWithVersion:4];
  472. [self collectGarbage];
  473. FSTAssertNotContains(@"foo/bar");
  474. FSTAssertNotContains(@"foo/bah");
  475. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  476. [self acknowledgeMutationWithVersion:5];
  477. [self collectGarbage];
  478. FSTAssertNotContains(@"foo/bar");
  479. FSTAssertNotContains(@"foo/bah");
  480. FSTAssertNotContains(@"foo/baz");
  481. }
  482. - (void)testCollectsGarbageAfterRejectedMutation {
  483. if ([self isTestBaseClass]) return;
  484. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"old"}, NO),
  485. @[ @1 ], @[])];
  486. [self writeMutation:FSTTestPatchMutation(@"foo/bar", @{@"foo" : @"bar"}, nil)];
  487. [self writeMutation:FSTTestSetMutation(@"foo/bah", @{@"foo" : @"bah"})];
  488. [self writeMutation:FSTTestDeleteMutation(@"foo/baz")];
  489. [self collectGarbage];
  490. FSTAssertContains(FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES));
  491. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  492. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  493. [self rejectMutation]; // patch mutation
  494. [self collectGarbage];
  495. FSTAssertNotContains(@"foo/bar");
  496. FSTAssertContains(FSTTestDoc(@"foo/bah", 0, @{@"foo" : @"bah"}, YES));
  497. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  498. [self rejectMutation]; // set mutation
  499. [self collectGarbage];
  500. FSTAssertNotContains(@"foo/bar");
  501. FSTAssertNotContains(@"foo/bah");
  502. FSTAssertContains(FSTTestDeletedDoc(@"foo/baz", 0));
  503. [self rejectMutation]; // delete mutation
  504. [self collectGarbage];
  505. FSTAssertNotContains(@"foo/bar");
  506. FSTAssertNotContains(@"foo/bah");
  507. FSTAssertNotContains(@"foo/baz");
  508. }
  509. - (void)testPinsDocumentsInTheLocalView {
  510. if ([self isTestBaseClass]) return;
  511. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
  512. [self allocateQuery:query];
  513. FSTAssertTargetID(2);
  514. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO),
  515. @[ @2 ], @[])];
  516. [self writeMutation:FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"})];
  517. [self collectGarbage];
  518. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
  519. FSTAssertContains(FSTTestDoc(@"foo/baz", 0, @{@"foo" : @"baz"}, YES));
  520. [self notifyLocalViewChanges:FSTTestViewChanges(query, @[ @"foo/bar", @"foo/baz" ], @[])];
  521. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO),
  522. @[], @[ @2 ])];
  523. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 2, @{@"foo" : @"baz"}, NO),
  524. @[ @1 ], @[])];
  525. [self acknowledgeMutationWithVersion:2];
  526. [self collectGarbage];
  527. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{@"foo" : @"bar"}, NO));
  528. FSTAssertContains(FSTTestDoc(@"foo/baz", 2, @{@"foo" : @"baz"}, NO));
  529. [self notifyLocalViewChanges:FSTTestViewChanges(query, @[], @[ @"foo/bar", @"foo/baz" ])];
  530. [self collectGarbage];
  531. FSTAssertNotContains(@"foo/bar");
  532. FSTAssertNotContains(@"foo/baz");
  533. }
  534. - (void)testThrowsAwayDocumentsWithUnknownTargetIDsImmediately {
  535. if ([self isTestBaseClass]) return;
  536. FSTTargetID targetID = 321;
  537. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 1, @{}, NO),
  538. @[ @(targetID) ], @[])];
  539. FSTAssertContains(FSTTestDoc(@"foo/bar", 1, @{}, NO));
  540. [self collectGarbage];
  541. FSTAssertNotContains(@"foo/bar");
  542. }
  543. - (void)testCanExecuteDocumentQueries {
  544. if ([self isTestBaseClass]) return;
  545. [self.localStore locallyWriteMutations:@[
  546. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"}),
  547. FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
  548. FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"})
  549. ]];
  550. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
  551. FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
  552. XCTAssertEqualObjects([docs values], @[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
  553. }
  554. - (void)testCanExecuteCollectionQueries {
  555. if ([self isTestBaseClass]) return;
  556. [self.localStore locallyWriteMutations:@[
  557. FSTTestSetMutation(@"fo/bar", @{@"fo" : @"bar"}),
  558. FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"}),
  559. FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
  560. FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"}),
  561. FSTTestSetMutation(@"fooo/blah", @{@"fooo" : @"blah"})
  562. ]];
  563. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
  564. FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
  565. XCTAssertEqualObjects([docs values], (@[
  566. FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES),
  567. FSTTestDoc(@"foo/baz", 0, @{@"foo" : @"baz"}, YES)
  568. ]));
  569. }
  570. - (void)testCanExecuteMixedCollectionQueries {
  571. if ([self isTestBaseClass]) return;
  572. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
  573. [self allocateQuery:query];
  574. FSTAssertTargetID(2);
  575. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
  576. @[ @2 ], @[])];
  577. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
  578. @[ @2 ], @[])];
  579. [self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
  580. FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
  581. XCTAssertEqualObjects([docs values], (@[
  582. FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
  583. FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
  584. FSTTestDoc(@"foo/bonk", 0, @{@"a" : @"b"}, YES)
  585. ]));
  586. }
  587. - (void)testPersistsResumeTokens {
  588. if ([self isTestBaseClass]) return;
  589. // This test only works in the absence of the FSTEagerGarbageCollector.
  590. [self restartWithNoopGarbageCollector];
  591. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
  592. FSTQueryData *queryData = [self.localStore allocateQuery:query];
  593. FSTBoxedTargetID *targetID = @(queryData.targetID);
  594. NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1000);
  595. FSTWatchChange *watchChange =
  596. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  597. targetIDs:@[ targetID ]
  598. resumeToken:resumeToken];
  599. NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *listens =
  600. [NSMutableDictionary dictionary];
  601. listens[targetID] = queryData;
  602. NSMutableDictionary<FSTBoxedTargetID *, NSNumber *> *pendingResponses =
  603. [NSMutableDictionary dictionary];
  604. FSTWatchChangeAggregator *aggregator =
  605. [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:FSTTestVersion(1000)
  606. listenTargets:listens
  607. pendingTargetResponses:pendingResponses];
  608. [aggregator addWatchChanges:@[ watchChange ]];
  609. FSTRemoteEvent *remoteEvent = [aggregator remoteEvent];
  610. [self applyRemoteEvent:remoteEvent];
  611. // Stop listening so that the query should become inactive (but persistent)
  612. [self.localStore releaseQuery:query];
  613. // Should come back with the same resume token
  614. FSTQueryData *queryData2 = [self.localStore allocateQuery:query];
  615. XCTAssertEqualObjects(queryData2.resumeToken, resumeToken);
  616. }
  617. - (void)testRemoteDocumentKeysForTarget {
  618. if ([self isTestBaseClass]) return;
  619. [self restartWithNoopGarbageCollector];
  620. FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
  621. [self allocateQuery:query];
  622. FSTAssertTargetID(2);
  623. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/baz", 10, @{@"a" : @"b"}, NO),
  624. @[ @2 ], @[])];
  625. [self applyRemoteEvent:FSTTestUpdateRemoteEvent(FSTTestDoc(@"foo/bar", 20, @{@"a" : @"b"}, NO),
  626. @[ @2 ], @[])];
  627. [self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
  628. FSTDocumentKeySet *keys = [self.localStore remoteDocumentKeysForTarget:2];
  629. FSTAssertEqualSets(keys, (@[ FSTTestDocKey(@"foo/bar"), FSTTestDocKey(@"foo/baz") ]));
  630. [self restartWithNoopGarbageCollector];
  631. keys = [self.localStore remoteDocumentKeysForTarget:2];
  632. FSTAssertEqualSets(keys, (@[ FSTTestDocKey(@"foo/bar"), FSTTestDocKey(@"foo/baz") ]));
  633. }
  634. @end
  635. NS_ASSUME_NONNULL_END