FSTRemoteEventTests.mm 49 KB

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