FSTRemoteEventTests.mm 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  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/Remote/FSTRemoteEvent.h"
  17. #import <XCTest/XCTest.h>
  18. #import "Firestore/Source/Core/FSTQuery.h"
  19. #import "Firestore/Source/Local/FSTQueryData.h"
  20. #import "Firestore/Source/Model/FSTDocument.h"
  21. #import "Firestore/Source/Remote/FSTWatchChange.h"
  22. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  23. #include "Firestore/core/src/firebase/firestore/remote/existence_filter.h"
  24. #import "Firestore/Example/Tests/Remote/FSTWatchChange+Testing.h"
  25. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  26. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  27. namespace testutil = firebase::firestore::testutil;
  28. using firebase::firestore::model::DocumentKey;
  29. using firebase::firestore::model::DocumentKeySet;
  30. using firebase::firestore::model::SnapshotVersion;
  31. using firebase::firestore::remote::ExistenceFilter;
  32. NS_ASSUME_NONNULL_BEGIN
  33. @interface FSTRemoteEventTests : XCTestCase
  34. @end
  35. @implementation FSTRemoteEventTests {
  36. NSData *_resumeToken1;
  37. NSMutableDictionary<NSNumber *, NSNumber *> *_noOutstandingResponses;
  38. FSTTestTargetMetadataProvider *_targetMetadataProvider;
  39. }
  40. - (void)setUp {
  41. _resumeToken1 = [@"resume1" dataUsingEncoding:NSUTF8StringEncoding];
  42. _noOutstandingResponses = [NSMutableDictionary dictionary];
  43. _targetMetadataProvider = [FSTTestTargetMetadataProvider new];
  44. }
  45. /**
  46. * Creates a map with query data for the provided target IDs. All targets are considered active
  47. * and query a collection named "coll".
  48. */
  49. - (NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)queryDataForTargets:
  50. (NSArray<FSTBoxedTargetID *> *)targetIDs {
  51. NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *targets =
  52. [NSMutableDictionary dictionary];
  53. for (FSTBoxedTargetID *targetID in targetIDs) {
  54. FSTQuery *query = FSTTestQuery("coll");
  55. targets[targetID] = [[FSTQueryData alloc] initWithQuery:query
  56. targetID:targetID.intValue
  57. listenSequenceNumber:0
  58. purpose:FSTQueryPurposeListen];
  59. }
  60. return targets;
  61. }
  62. /**
  63. * Creates a map with query data for the provided target IDs. All targets are marked as limbo
  64. * queries for the document at "coll/limbo".
  65. */
  66. - (NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)queryDataForLimboTargets:
  67. (NSArray<FSTBoxedTargetID *> *)targetIDs {
  68. NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *targets =
  69. [NSMutableDictionary dictionary];
  70. for (FSTBoxedTargetID *targetID in targetIDs) {
  71. FSTQuery *query = FSTTestQuery("coll/limbo");
  72. targets[targetID] = [[FSTQueryData alloc] initWithQuery:query
  73. targetID:targetID.intValue
  74. listenSequenceNumber:0
  75. purpose:FSTQueryPurposeLimboResolution];
  76. }
  77. return targets;
  78. }
  79. /**
  80. * Creates an aggregator initialized with the set of provided FSTWatchChanges. Tests can add further
  81. * changes via `handleDocumentChange`, `handleTargetChange` and `handleExistenceFilterChange`.
  82. *
  83. * @param targetMap A map of query data for all active targets. The map must include an entry for
  84. * every target referenced by any of the watch changes.
  85. * @param outstandingResponses The number of outstanding ACKs a target has to receive before it is
  86. * considered active, or `_noOutstandingResponses` if all targets are already active.
  87. * @param existingKeys The set of documents that are considered synced with the test targets as
  88. * part of a previous listen. To modify this set during test execution, invoke
  89. * `[_targetMetadataProvider setSyncedKeys:forQueryData:]`.
  90. * @param watchChanges The watch changes to apply before returning the aggregator. Supported
  91. * changes are FSTDocumentWatchChange and FSTWatchTargetChange.
  92. */
  93. - (FSTWatchChangeAggregator *)
  94. aggregatorWithTargetMap:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)targetMap
  95. outstandingResponses:
  96. (nullable NSDictionary<FSTBoxedTargetID *, NSNumber *> *)outstandingResponses
  97. existingKeys:(DocumentKeySet)existingKeys
  98. changes:(NSArray<FSTWatchChange *> *)watchChanges {
  99. FSTWatchChangeAggregator *aggregator =
  100. [[FSTWatchChangeAggregator alloc] initWithTargetMetadataProvider:_targetMetadataProvider];
  101. NSMutableArray<FSTBoxedTargetID *> *targetIDs = [NSMutableArray array];
  102. [targetMap enumerateKeysAndObjectsUsingBlock:^(FSTBoxedTargetID *targetID,
  103. FSTQueryData *queryData, BOOL *stop) {
  104. [targetIDs addObject:targetID];
  105. [_targetMetadataProvider setSyncedKeys:existingKeys forQueryData:queryData];
  106. }];
  107. [outstandingResponses
  108. enumerateKeysAndObjectsUsingBlock:^(FSTBoxedTargetID *targetID, NSNumber *count, BOOL *stop) {
  109. for (int i = 0; i < count.intValue; ++i) {
  110. [aggregator recordTargetRequest:targetID];
  111. }
  112. }];
  113. for (FSTWatchChange *change in watchChanges) {
  114. if ([change isKindOfClass:[FSTDocumentWatchChange class]]) {
  115. [aggregator handleDocumentChange:(FSTDocumentWatchChange *)change];
  116. } else if ([change isKindOfClass:[FSTWatchTargetChange class]]) {
  117. [aggregator handleTargetChange:(FSTWatchTargetChange *)change];
  118. } else {
  119. HARD_ASSERT("Encountered unexpected type of FSTWatchChange");
  120. }
  121. }
  122. [aggregator handleTargetChange:[[FSTWatchTargetChange alloc]
  123. initWithState:FSTWatchTargetChangeStateNoChange
  124. targetIDs:targetIDs
  125. resumeToken:_resumeToken1
  126. cause:nil]];
  127. return aggregator;
  128. }
  129. /**
  130. * Creates a single remote event that includes target changes for all provided FSTWatchChanges.
  131. *
  132. * @param snapshotVersion The version at which to create the remote event. This corresponds to the
  133. * snapshot version provided by the NO_CHANGE event.
  134. * @param targetMap A map of query data for all active targets. The map must include an entry for
  135. * every target referenced by any of the watch changes.
  136. * @param outstandingResponses The number of outstanding ACKs a target has to receive before it is
  137. * considered active, or `_noOutstandingResponses` if all targets are already active.
  138. * @param existingKeys The set of documents that are considered synced with the test targets as
  139. * part of a previous listen.
  140. * @param watchChanges The watch changes to apply before creating the remote event. Supported
  141. * changes are FSTDocumentWatchChange and FSTWatchTargetChange.
  142. */
  143. - (FSTRemoteEvent *)
  144. remoteEventAtSnapshotVersion:(FSTTestSnapshotVersion)snapshotVersion
  145. targetMap:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)targetMap
  146. outstandingResponses:
  147. (nullable NSDictionary<FSTBoxedTargetID *, NSNumber *> *)outstandingResponses
  148. existingKeys:(DocumentKeySet)existingKeys
  149. changes:(NSArray<FSTWatchChange *> *)watchChanges {
  150. FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
  151. outstandingResponses:outstandingResponses
  152. existingKeys:existingKeys
  153. changes:watchChanges];
  154. return [aggregator remoteEventAtSnapshotVersion:testutil::Version(snapshotVersion)];
  155. }
  156. - (void)testWillAccumulateDocumentAddedAndRemovedEvents {
  157. // The target map that contains an entry for every target in this test. If a target ID is omitted,
  158. // the target is considered inactive and FSTTestTargetMetadataProvider will fail on access.
  159. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  160. [self queryDataForTargets:@[ @1, @2, @3, @4, @5, @6 ]];
  161. FSTDocument *existingDoc = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  162. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2, @3 ]
  163. removedTargetIDs:@[ @4, @5, @6 ]
  164. documentKey:existingDoc.key
  165. document:existingDoc];
  166. FSTDocument *newDoc = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced);
  167. FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @4 ]
  168. removedTargetIDs:@[ @2, @6 ]
  169. documentKey:newDoc.key
  170. document:newDoc];
  171. // Create a remote event that includes both `change1` and `change2` as well as a NO_CHANGE event
  172. // with the default resume token (`_resumeToken1`).
  173. // As `existingDoc` is provided as an existing key, any updates to this document will be treated
  174. // as modifications rather than adds.
  175. FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
  176. targetMap:targetMap
  177. outstandingResponses:_noOutstandingResponses
  178. existingKeys:DocumentKeySet{existingDoc.key}
  179. changes:@[ change1, change2 ]];
  180. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  181. XCTAssertEqual(event.documentUpdates.size(), 2);
  182. XCTAssertEqualObjects(event.documentUpdates.at(existingDoc.key), existingDoc);
  183. XCTAssertEqualObjects(event.documentUpdates.at(newDoc.key), newDoc);
  184. // 'change1' and 'change2' affect six different targets
  185. XCTAssertEqual(event.targetChanges.size(), 6);
  186. FSTTargetChange *targetChange1 =
  187. FSTTestTargetChange(DocumentKeySet{newDoc.key}, DocumentKeySet{existingDoc.key},
  188. DocumentKeySet{}, _resumeToken1, NO);
  189. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
  190. FSTTargetChange *targetChange2 = FSTTestTargetChange(
  191. DocumentKeySet{}, DocumentKeySet{existingDoc.key}, DocumentKeySet{}, _resumeToken1, NO);
  192. XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
  193. FSTTargetChange *targetChange3 = FSTTestTargetChange(
  194. DocumentKeySet{}, DocumentKeySet{existingDoc.key}, DocumentKeySet{}, _resumeToken1, NO);
  195. XCTAssertEqualObjects(event.targetChanges.at(3), targetChange3);
  196. FSTTargetChange *targetChange4 =
  197. FSTTestTargetChange(DocumentKeySet{newDoc.key}, DocumentKeySet{},
  198. DocumentKeySet{existingDoc.key}, _resumeToken1, NO);
  199. XCTAssertEqualObjects(event.targetChanges.at(4), targetChange4);
  200. FSTTargetChange *targetChange5 = FSTTestTargetChange(
  201. DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{existingDoc.key}, _resumeToken1, NO);
  202. XCTAssertEqualObjects(event.targetChanges.at(5), targetChange5);
  203. FSTTargetChange *targetChange6 = FSTTestTargetChange(
  204. DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{existingDoc.key}, _resumeToken1, NO);
  205. XCTAssertEqualObjects(event.targetChanges.at(6), targetChange6);
  206. }
  207. - (void)testWillIgnoreEventsForPendingTargets {
  208. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
  209. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  210. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  211. removedTargetIDs:@[]
  212. documentKey:doc1.key
  213. document:doc1];
  214. FSTWatchChange *change2 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved
  215. targetIDs:@[ @1 ]
  216. cause:nil];
  217. FSTWatchChange *change3 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateAdded
  218. targetIDs:@[ @1 ]
  219. cause:nil];
  220. FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced);
  221. FSTWatchChange *change4 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  222. removedTargetIDs:@[]
  223. documentKey:doc2.key
  224. document:doc2];
  225. // We're waiting for the unwatch and watch ack
  226. NSDictionary<NSNumber *, NSNumber *> *outstandingResponses = @{@1 : @2};
  227. FSTRemoteEvent *event =
  228. [self remoteEventAtSnapshotVersion:3
  229. targetMap:targetMap
  230. outstandingResponses:outstandingResponses
  231. existingKeys:DocumentKeySet {}
  232. changes:@[ change1, change2, change3, change4 ]];
  233. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  234. // doc1 is ignored because it was part of an inactive target, but doc2 is in the changes
  235. // because it become active.
  236. XCTAssertEqual(event.documentUpdates.size(), 1);
  237. XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
  238. XCTAssertEqual(event.targetChanges.size(), 1);
  239. }
  240. - (void)testWillIgnoreEventsForRemovedTargets {
  241. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[]];
  242. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  243. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  244. removedTargetIDs:@[]
  245. documentKey:doc1.key
  246. document:doc1];
  247. FSTWatchChange *change2 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved
  248. targetIDs:@[ @1 ]
  249. cause:nil];
  250. // We're waiting for the unwatch ack
  251. NSDictionary<NSNumber *, NSNumber *> *outstandingResponses = @{@1 : @1};
  252. FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
  253. targetMap:targetMap
  254. outstandingResponses:outstandingResponses
  255. existingKeys:DocumentKeySet {}
  256. changes:@[ change1, change2 ]];
  257. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  258. // doc1 is ignored because it was part of an inactive target
  259. XCTAssertEqual(event.documentUpdates.size(), 0);
  260. // Target 1 is ignored because it was removed
  261. XCTAssertEqual(event.targetChanges.size(), 0);
  262. }
  263. - (void)testWillKeepResetMappingEvenWithUpdates {
  264. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
  265. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  266. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  267. removedTargetIDs:@[]
  268. documentKey:doc1.key
  269. document:doc1];
  270. // Reset stream, ignoring doc1
  271. FSTWatchChange *change2 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
  272. targetIDs:@[ @1 ]
  273. cause:nil];
  274. // Add doc2, doc3
  275. FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced);
  276. FSTWatchChange *change3 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  277. removedTargetIDs:@[]
  278. documentKey:doc2.key
  279. document:doc2];
  280. FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{@"value" : @3}, FSTDocumentStateSynced);
  281. FSTWatchChange *change4 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  282. removedTargetIDs:@[]
  283. documentKey:doc3.key
  284. document:doc3];
  285. // Remove doc2 again, should not show up in reset mapping
  286. FSTWatchChange *change5 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
  287. removedTargetIDs:@[ @1 ]
  288. documentKey:doc2.key
  289. document:doc2];
  290. FSTRemoteEvent *event =
  291. [self remoteEventAtSnapshotVersion:3
  292. targetMap:targetMap
  293. outstandingResponses:_noOutstandingResponses
  294. existingKeys:DocumentKeySet{doc1.key}
  295. changes:@[ change1, change2, change3, change4, change5 ]];
  296. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  297. XCTAssertEqual(event.documentUpdates.size(), 3);
  298. XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
  299. XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
  300. XCTAssertEqualObjects(event.documentUpdates.at(doc3.key), doc3);
  301. XCTAssertEqual(event.targetChanges.size(), 1);
  302. // Only doc3 is part of the new mapping
  303. FSTTargetChange *expectedChange = FSTTestTargetChange(
  304. DocumentKeySet{doc3.key}, DocumentKeySet{}, DocumentKeySet{doc1.key}, _resumeToken1, NO);
  305. XCTAssertEqualObjects(event.targetChanges.at(1), expectedChange);
  306. }
  307. - (void)testWillHandleSingleReset {
  308. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
  309. // Reset target
  310. FSTWatchTargetChange *change =
  311. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
  312. targetIDs:@[ @1 ]
  313. cause:nil];
  314. FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
  315. outstandingResponses:_noOutstandingResponses
  316. existingKeys:DocumentKeySet {}
  317. changes:@[]];
  318. [aggregator handleTargetChange:change];
  319. FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  320. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  321. XCTAssertEqual(event.documentUpdates.size(), 0);
  322. XCTAssertEqual(event.targetChanges.size(), 1);
  323. // Reset mapping is empty
  324. FSTTargetChange *expectedChange =
  325. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, [NSData data], NO);
  326. XCTAssertEqualObjects(event.targetChanges.at(1), expectedChange);
  327. }
  328. - (void)testWillHandleTargetAddAndRemovalInSameBatch {
  329. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  330. [self queryDataForTargets:@[ @1, @2 ]];
  331. FSTDocument *doc1a = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  332. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  333. removedTargetIDs:@[ @2 ]
  334. documentKey:doc1a.key
  335. document:doc1a];
  336. FSTDocument *doc1b = FSTTestDoc("docs/1", 1, @{@"value" : @2}, FSTDocumentStateSynced);
  337. FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ]
  338. removedTargetIDs:@[ @1 ]
  339. documentKey:doc1b.key
  340. document:doc1b];
  341. FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
  342. targetMap:targetMap
  343. outstandingResponses:_noOutstandingResponses
  344. existingKeys:DocumentKeySet{doc1a.key}
  345. changes:@[ change1, change2 ]];
  346. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  347. XCTAssertEqual(event.documentUpdates.size(), 1);
  348. XCTAssertEqualObjects(event.documentUpdates.at(doc1b.key), doc1b);
  349. XCTAssertEqual(event.targetChanges.size(), 2);
  350. FSTTargetChange *targetChange1 = FSTTestTargetChange(
  351. DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{doc1b.key}, _resumeToken1, NO);
  352. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
  353. FSTTargetChange *targetChange2 = FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{doc1b.key},
  354. DocumentKeySet{}, _resumeToken1, NO);
  355. XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
  356. }
  357. - (void)testTargetCurrentChangeWillMarkTheTargetCurrent {
  358. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
  359. FSTWatchChange *change = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  360. targetIDs:@[ @1 ]
  361. resumeToken:_resumeToken1];
  362. FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
  363. targetMap:targetMap
  364. outstandingResponses:_noOutstandingResponses
  365. existingKeys:DocumentKeySet {}
  366. changes:@[ change ]];
  367. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  368. XCTAssertEqual(event.documentUpdates.size(), 0);
  369. XCTAssertEqual(event.targetChanges.size(), 1);
  370. FSTTargetChange *targetChange =
  371. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, YES);
  372. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
  373. }
  374. - (void)testTargetAddedChangeWillResetPreviousState {
  375. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  376. [self queryDataForTargets:@[ @1, @3 ]];
  377. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  378. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @3 ]
  379. removedTargetIDs:@[ @2 ]
  380. documentKey:doc1.key
  381. document:doc1];
  382. FSTWatchChange *change2 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  383. targetIDs:@[ @1, @2, @3 ]
  384. resumeToken:_resumeToken1];
  385. FSTWatchChange *change3 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved
  386. targetIDs:@[ @1 ]
  387. cause:nil];
  388. FSTWatchChange *change4 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateRemoved
  389. targetIDs:@[ @2 ]
  390. cause:nil];
  391. FSTWatchChange *change5 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateAdded
  392. targetIDs:@[ @1 ]
  393. cause:nil];
  394. FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced);
  395. FSTWatchChange *change6 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  396. removedTargetIDs:@[ @3 ]
  397. documentKey:doc2.key
  398. document:doc2];
  399. NSDictionary<NSNumber *, NSNumber *> *outstandingResponses = @{@1 : @2, @2 : @1};
  400. FSTRemoteEvent *event =
  401. [self remoteEventAtSnapshotVersion:3
  402. targetMap:targetMap
  403. outstandingResponses:outstandingResponses
  404. existingKeys:DocumentKeySet{doc2.key}
  405. changes:@[ change1, change2, change3, change4, change5, change6 ]];
  406. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  407. XCTAssertEqual(event.documentUpdates.size(), 2);
  408. XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
  409. XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
  410. // target 1 and 3 are affected (1 because of re-add), target 2 is not because of remove
  411. XCTAssertEqual(event.targetChanges.size(), 2);
  412. // doc1 was before the remove, so it does not show up in the mapping.
  413. // Current was before the remove.
  414. FSTTargetChange *targetChange1 = FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{doc2.key},
  415. DocumentKeySet{}, _resumeToken1, NO);
  416. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
  417. // Doc1 was before the remove
  418. // Current was before the remove
  419. FSTTargetChange *targetChange3 = FSTTestTargetChange(
  420. DocumentKeySet{doc1.key}, DocumentKeySet{}, DocumentKeySet{doc2.key}, _resumeToken1, YES);
  421. XCTAssertEqualObjects(event.targetChanges.at(3), targetChange3);
  422. }
  423. - (void)testNoChangeWillStillMarkTheAffectedTargets {
  424. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
  425. FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
  426. outstandingResponses:_noOutstandingResponses
  427. existingKeys:DocumentKeySet {}
  428. changes:@[]];
  429. FSTWatchTargetChange *change =
  430. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
  431. targetIDs:@[ @1 ]
  432. resumeToken:_resumeToken1];
  433. [aggregator handleTargetChange:change];
  434. FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  435. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  436. XCTAssertEqual(event.documentUpdates.size(), 0);
  437. XCTAssertEqual(event.targetChanges.size(), 1);
  438. FSTTargetChange *targetChange =
  439. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, NO);
  440. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
  441. }
  442. - (void)testExistenceFilterMismatchClearsTarget {
  443. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  444. [self queryDataForTargets:@[ @1, @2 ]];
  445. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  446. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  447. removedTargetIDs:@[]
  448. documentKey:doc1.key
  449. document:doc1];
  450. FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced);
  451. FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  452. removedTargetIDs:@[]
  453. documentKey:doc2.key
  454. document:doc2];
  455. FSTWatchChange *change3 = [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  456. targetIDs:@[ @1 ]
  457. resumeToken:_resumeToken1];
  458. FSTWatchChangeAggregator *aggregator =
  459. [self aggregatorWithTargetMap:targetMap
  460. outstandingResponses:_noOutstandingResponses
  461. existingKeys:DocumentKeySet{doc1.key, doc2.key}
  462. changes:@[ change1, change2, change3 ]];
  463. FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  464. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  465. XCTAssertEqual(event.documentUpdates.size(), 2);
  466. XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
  467. XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
  468. XCTAssertEqual(event.targetChanges.size(), 2);
  469. FSTTargetChange *targetChange1 = FSTTestTargetChange(
  470. DocumentKeySet{}, DocumentKeySet{doc1.key, doc2.key}, DocumentKeySet{}, _resumeToken1, YES);
  471. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
  472. FSTTargetChange *targetChange2 =
  473. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, NO);
  474. XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
  475. // The existence filter mismatch will remove the document from target 1,
  476. // but not synthesize a document delete.
  477. FSTExistenceFilterWatchChange *change4 =
  478. [FSTExistenceFilterWatchChange changeWithFilter:ExistenceFilter{1} targetID:1];
  479. [aggregator handleExistenceFilter:change4];
  480. event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(4)];
  481. FSTTargetChange *targetChange3 = FSTTestTargetChange(
  482. DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{doc1.key, doc2.key}, [NSData data], NO);
  483. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange3);
  484. XCTAssertEqual(event.targetChanges.size(), 1);
  485. XCTAssertEqual(event.targetMismatches.size(), 1);
  486. XCTAssertEqual(event.documentUpdates.size(), 0);
  487. }
  488. - (void)testExistenceFilterMismatchRemovesCurrentChanges {
  489. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
  490. FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
  491. outstandingResponses:_noOutstandingResponses
  492. existingKeys:DocumentKeySet {}
  493. changes:@[]];
  494. FSTWatchTargetChange *markCurrent =
  495. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  496. targetIDs:@[ @1 ]
  497. resumeToken:_resumeToken1];
  498. [aggregator handleTargetChange:markCurrent];
  499. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  500. FSTDocumentWatchChange *addDoc = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  501. removedTargetIDs:@[]
  502. documentKey:doc1.key
  503. document:doc1];
  504. [aggregator handleDocumentChange:addDoc];
  505. // The existence filter mismatch will remove the document from target 1, but not synthesize a
  506. // document delete.
  507. FSTExistenceFilterWatchChange *existenceFilter =
  508. [FSTExistenceFilterWatchChange changeWithFilter:ExistenceFilter{0} targetID:1];
  509. [aggregator handleExistenceFilter:existenceFilter];
  510. FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  511. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  512. XCTAssertEqual(event.documentUpdates.size(), 1);
  513. XCTAssertEqual(event.targetMismatches.size(), 1);
  514. XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
  515. XCTAssertEqual(event.targetChanges.size(), 1);
  516. FSTTargetChange *targetChange1 =
  517. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, [NSData data], NO);
  518. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
  519. }
  520. - (void)testDocumentUpdate {
  521. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap = [self queryDataForTargets:@[ @1 ]];
  522. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"value" : @1}, FSTDocumentStateSynced);
  523. FSTWatchChange *change1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  524. removedTargetIDs:@[]
  525. documentKey:doc1.key
  526. document:doc1];
  527. FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{@"value" : @2}, FSTDocumentStateSynced);
  528. FSTWatchChange *change2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  529. removedTargetIDs:@[]
  530. documentKey:doc2.key
  531. document:doc2];
  532. FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
  533. outstandingResponses:_noOutstandingResponses
  534. existingKeys:DocumentKeySet {}
  535. changes:@[ change1, change2 ]];
  536. FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  537. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  538. XCTAssertEqual(event.documentUpdates.size(), 2);
  539. XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
  540. XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
  541. [_targetMetadataProvider setSyncedKeys:DocumentKeySet{doc1.key, doc2.key}
  542. forQueryData:targetMap[@1]];
  543. FSTDeletedDocument *deletedDoc1 = [FSTDeletedDocument documentWithKey:doc1.key
  544. version:testutil::Version(3)
  545. hasCommittedMutations:NO];
  546. FSTDocumentWatchChange *change3 =
  547. [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
  548. removedTargetIDs:@[ @1 ]
  549. documentKey:deletedDoc1.key
  550. document:deletedDoc1];
  551. [aggregator handleDocumentChange:change3];
  552. FSTDocument *updatedDoc2 = FSTTestDoc("docs/2", 3, @{@"value" : @2}, FSTDocumentStateSynced);
  553. FSTDocumentWatchChange *change4 =
  554. [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  555. removedTargetIDs:@[]
  556. documentKey:updatedDoc2.key
  557. document:updatedDoc2];
  558. [aggregator handleDocumentChange:change4];
  559. FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{@"value" : @3}, FSTDocumentStateSynced);
  560. FSTDocumentWatchChange *change5 =
  561. [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  562. removedTargetIDs:@[]
  563. documentKey:doc3.key
  564. document:doc3];
  565. [aggregator handleDocumentChange:change5];
  566. event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  567. XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
  568. XCTAssertEqual(event.documentUpdates.size(), 3);
  569. // doc1 is replaced
  570. XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), deletedDoc1);
  571. // doc2 is updated
  572. XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), updatedDoc2);
  573. // doc3 is new
  574. XCTAssertEqualObjects(event.documentUpdates.at(doc3.key), doc3);
  575. // Target is unchanged
  576. XCTAssertEqual(event.targetChanges.size(), 1);
  577. FSTTargetChange *targetChange =
  578. FSTTestTargetChange(DocumentKeySet{doc3.key}, DocumentKeySet{updatedDoc2.key},
  579. DocumentKeySet{deletedDoc1.key}, _resumeToken1, NO);
  580. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
  581. }
  582. - (void)testResumeTokensHandledPerTarget {
  583. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  584. [self queryDataForTargets:@[ @1, @2 ]];
  585. FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
  586. outstandingResponses:_noOutstandingResponses
  587. existingKeys:DocumentKeySet {}
  588. changes:@[]];
  589. FSTWatchTargetChange *change1 =
  590. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  591. targetIDs:@[ @1 ]
  592. resumeToken:_resumeToken1];
  593. [aggregator handleTargetChange:change1];
  594. NSData *resumeToken2 = [@"resume2" dataUsingEncoding:NSUTF8StringEncoding];
  595. FSTWatchTargetChange *change2 =
  596. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  597. targetIDs:@[ @2 ]
  598. resumeToken:resumeToken2];
  599. [aggregator handleTargetChange:change2];
  600. FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  601. XCTAssertEqual(event.targetChanges.size(), 2);
  602. FSTTargetChange *targetChange1 =
  603. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, _resumeToken1, YES);
  604. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
  605. FSTTargetChange *targetChange2 =
  606. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, resumeToken2, YES);
  607. XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
  608. }
  609. - (void)testLastResumeTokenWins {
  610. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  611. [self queryDataForTargets:@[ @1, @2 ]];
  612. FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargetMap:targetMap
  613. outstandingResponses:_noOutstandingResponses
  614. existingKeys:DocumentKeySet {}
  615. changes:@[]];
  616. FSTWatchTargetChange *change1 =
  617. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
  618. targetIDs:@[ @1 ]
  619. resumeToken:_resumeToken1];
  620. [aggregator handleTargetChange:change1];
  621. NSData *resumeToken2 = [@"resume2" dataUsingEncoding:NSUTF8StringEncoding];
  622. FSTWatchTargetChange *change2 =
  623. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
  624. targetIDs:@[ @1 ]
  625. resumeToken:resumeToken2];
  626. [aggregator handleTargetChange:change2];
  627. NSData *resumeToken3 = [@"resume3" dataUsingEncoding:NSUTF8StringEncoding];
  628. FSTWatchTargetChange *change3 =
  629. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange
  630. targetIDs:@[ @2 ]
  631. resumeToken:resumeToken3];
  632. [aggregator handleTargetChange:change3];
  633. FSTRemoteEvent *event = [aggregator remoteEventAtSnapshotVersion:testutil::Version(3)];
  634. XCTAssertEqual(event.targetChanges.size(), 2);
  635. FSTTargetChange *targetChange1 =
  636. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, resumeToken2, YES);
  637. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange1);
  638. FSTTargetChange *targetChange2 =
  639. FSTTestTargetChange(DocumentKeySet{}, DocumentKeySet{}, DocumentKeySet{}, resumeToken3, NO);
  640. XCTAssertEqualObjects(event.targetChanges.at(2), targetChange2);
  641. }
  642. - (void)testSynthesizeDeletes {
  643. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  644. [self queryDataForLimboTargets:@[ @1 ]];
  645. DocumentKey limboKey = testutil::Key("coll/limbo");
  646. FSTWatchChange *resolveLimboTarget =
  647. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1 ]];
  648. FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
  649. targetMap:targetMap
  650. outstandingResponses:_noOutstandingResponses
  651. existingKeys:DocumentKeySet {}
  652. changes:@[ resolveLimboTarget ]];
  653. FSTDeletedDocument *expected = [FSTDeletedDocument documentWithKey:limboKey
  654. version:event.snapshotVersion
  655. hasCommittedMutations:NO];
  656. XCTAssertEqualObjects(event.documentUpdates.at(limboKey), expected);
  657. XCTAssertTrue(event.limboDocumentChanges.contains(limboKey));
  658. }
  659. - (void)testDoesntSynthesizeDeletesForWrongState {
  660. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  661. [self queryDataForLimboTargets:@[ @1 ]];
  662. FSTWatchChange *wrongState =
  663. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange targetIDs:@[ @1 ]];
  664. FSTRemoteEvent *event = [self remoteEventAtSnapshotVersion:3
  665. targetMap:targetMap
  666. outstandingResponses:_noOutstandingResponses
  667. existingKeys:DocumentKeySet {}
  668. changes:@[ wrongState ]];
  669. XCTAssertEqual(event.documentUpdates.size(), 0);
  670. XCTAssertEqual(event.limboDocumentChanges.size(), 0);
  671. }
  672. - (void)testDoesntSynthesizeDeletesForExistingDoc {
  673. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  674. [self queryDataForLimboTargets:@[ @3 ]];
  675. FSTWatchChange *hasDocument =
  676. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @3 ]];
  677. FSTRemoteEvent *event =
  678. [self remoteEventAtSnapshotVersion:3
  679. targetMap:targetMap
  680. outstandingResponses:_noOutstandingResponses
  681. existingKeys:DocumentKeySet{FSTTestDocKey(@"coll/limbo")}
  682. changes:@[ hasDocument ]];
  683. XCTAssertEqual(event.documentUpdates.size(), 0);
  684. XCTAssertEqual(event.limboDocumentChanges.size(), 0);
  685. }
  686. - (void)testSeparatesDocumentUpdates {
  687. NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  688. [self queryDataForLimboTargets:@[ @1 ]];
  689. FSTDocument *newDoc = FSTTestDoc("docs/new", 1, @{@"key" : @"value"}, FSTDocumentStateSynced);
  690. FSTWatchChange *newDocChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  691. removedTargetIDs:@[]
  692. documentKey:newDoc.key
  693. document:newDoc];
  694. FSTDocument *existingDoc =
  695. FSTTestDoc("docs/existing", 1, @{@"some" : @"data"}, FSTDocumentStateSynced);
  696. FSTWatchChange *existingDocChange =
  697. [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  698. removedTargetIDs:@[]
  699. documentKey:existingDoc.key
  700. document:existingDoc];
  701. FSTDeletedDocument *deletedDoc = FSTTestDeletedDoc("docs/deleted", 1, NO);
  702. FSTWatchChange *deletedDocChange =
  703. [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
  704. removedTargetIDs:@[ @1 ]
  705. documentKey:deletedDoc.key
  706. document:deletedDoc];
  707. FSTDeletedDocument *missingDoc = FSTTestDeletedDoc("docs/missing", 1, NO);
  708. FSTWatchChange *missingDocChange =
  709. [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
  710. removedTargetIDs:@[ @1 ]
  711. documentKey:missingDoc.key
  712. document:missingDoc];
  713. FSTRemoteEvent *event = [self
  714. remoteEventAtSnapshotVersion:3
  715. targetMap:targetMap
  716. outstandingResponses:_noOutstandingResponses
  717. existingKeys:DocumentKeySet{existingDoc.key, deletedDoc.key}
  718. changes:@[
  719. newDocChange, existingDocChange, deletedDocChange, missingDocChange
  720. ]];
  721. FSTTargetChange *targetChange =
  722. FSTTestTargetChange(DocumentKeySet{newDoc.key}, DocumentKeySet{existingDoc.key},
  723. DocumentKeySet{deletedDoc.key}, _resumeToken1, NO);
  724. XCTAssertEqualObjects(event.targetChanges.at(1), targetChange);
  725. }
  726. - (void)testTracksLimboDocuments {
  727. NSMutableDictionary<FSTBoxedTargetID *, FSTQueryData *> *targetMap =
  728. [NSMutableDictionary dictionary];
  729. [targetMap addEntriesFromDictionary:[self queryDataForTargets:@[ @1 ]]];
  730. [targetMap addEntriesFromDictionary:[self queryDataForLimboTargets:@[ @2 ]]];
  731. // Add 3 docs: 1 is limbo and non-limbo, 2 is limbo-only, 3 is non-limbo
  732. FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"key" : @"value"}, FSTDocumentStateSynced);
  733. FSTDocument *doc2 = FSTTestDoc("docs/2", 1, @{@"key" : @"value"}, FSTDocumentStateSynced);
  734. FSTDocument *doc3 = FSTTestDoc("docs/3", 1, @{@"key" : @"value"}, FSTDocumentStateSynced);
  735. // Target 2 is a limbo target
  736. FSTWatchChange *docChange1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2 ]
  737. removedTargetIDs:@[]
  738. documentKey:doc1.key
  739. document:doc1];
  740. FSTWatchChange *docChange2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ]
  741. removedTargetIDs:@[]
  742. documentKey:doc2.key
  743. document:doc2];
  744. FSTWatchChange *docChange3 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
  745. removedTargetIDs:@[]
  746. documentKey:doc3.key
  747. document:doc3];
  748. FSTWatchChange *targetsChange =
  749. [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1, @2 ]];
  750. FSTRemoteEvent *event =
  751. [self remoteEventAtSnapshotVersion:3
  752. targetMap:targetMap
  753. outstandingResponses:_noOutstandingResponses
  754. existingKeys:DocumentKeySet {}
  755. changes:@[ docChange1, docChange2, docChange3, targetsChange ]];
  756. DocumentKeySet limboDocChanges = event.limboDocumentChanges;
  757. // Doc1 is in both limbo and non-limbo targets, therefore not tracked as limbo
  758. XCTAssertFalse(limboDocChanges.contains(doc1.key));
  759. // Doc2 is only in the limbo target, so is tracked as a limbo document
  760. XCTAssertTrue(limboDocChanges.contains(doc2.key));
  761. // Doc3 is only in the non-limbo target, therefore not tracked as limbo
  762. XCTAssertFalse(limboDocChanges.contains(doc3.key));
  763. }
  764. @end
  765. NS_ASSUME_NONNULL_END