FSTLocalStoreTests.mm 47 KB

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