FSTLocalStoreTests.mm 50 KB

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