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