|
22 | 22 | #import "Firestore/Example/Tests/Util/FSTHelpers.h"
|
23 | 23 | #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
|
24 | 24 |
|
| 25 | +namespace { |
| 26 | + |
| 27 | +NSArray<NSString *> *SortedStringsNotIn(NSSet<NSString *> *set, NSSet<NSString *> *remove) { |
| 28 | + NSMutableSet<NSString *> *mutableSet = [NSMutableSet setWithSet:set]; |
| 29 | + [mutableSet minusSet:remove]; |
| 30 | + return [mutableSet.allObjects sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; |
| 31 | +} |
| 32 | + |
| 33 | +} // namespace |
| 34 | + |
25 | 35 | @interface FIRQueryTests : FSTIntegrationTestCase
|
26 | 36 | @end
|
27 | 37 |
|
@@ -1180,4 +1190,88 @@ - (void)testOrderByEquality {
|
1180 | 1190 | matchesResult:@[ @"doc6", @"doc3" ]];
|
1181 | 1191 | }
|
1182 | 1192 |
|
| 1193 | +- (void)testResumingAQueryShouldUseExistenceFilterToDetectDeletes { |
| 1194 | + // Prepare the names and contents of the 100 documents to create. |
| 1195 | + NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *testDocs = |
| 1196 | + [[NSMutableDictionary alloc] init]; |
| 1197 | + for (int i = 0; i < 100; i++) { |
| 1198 | + [testDocs setValue:@{@"key" : @42} forKey:[NSString stringWithFormat:@"doc%@", @(1000 + i)]]; |
| 1199 | + } |
| 1200 | + |
| 1201 | + // Create 100 documents in a new collection. |
| 1202 | + FIRCollectionReference *collRef = [self collectionRefWithDocuments:testDocs]; |
| 1203 | + |
| 1204 | + // Run a query to populate the local cache with the 100 documents and a resume token. |
| 1205 | + FIRQuerySnapshot *querySnapshot1 = [self readDocumentSetForRef:collRef |
| 1206 | + source:FIRFirestoreSourceDefault]; |
| 1207 | + XCTAssertEqual(querySnapshot1.count, 100, @"querySnapshot1.count has an unexpected value"); |
| 1208 | + NSArray<FIRDocumentReference *> *createdDocuments = |
| 1209 | + FIRDocumentReferenceArrayFromQuerySnapshot(querySnapshot1); |
| 1210 | + |
| 1211 | + // Delete 50 of the 100 documents. Do this in a transaction, rather than |
| 1212 | + // [FIRDocumentReference deleteDocument], to avoid affecting the local cache. |
| 1213 | + NSSet<NSString *> *deletedDocumentIds; |
| 1214 | + { |
| 1215 | + NSMutableArray<NSString *> *deletedDocumentIdsAccumulator = [[NSMutableArray alloc] init]; |
| 1216 | + XCTestExpectation *expectation = [self expectationWithDescription:@"DeleteTransaction"]; |
| 1217 | + [collRef.firestore |
| 1218 | + runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **) { |
| 1219 | + for (decltype(createdDocuments.count) i = 0; i < createdDocuments.count; i += 2) { |
| 1220 | + FIRDocumentReference *documentToDelete = createdDocuments[i]; |
| 1221 | + [transaction deleteDocument:documentToDelete]; |
| 1222 | + [deletedDocumentIdsAccumulator addObject:documentToDelete.documentID]; |
| 1223 | + } |
| 1224 | + return @"document deletion successful"; |
| 1225 | + } |
| 1226 | + completion:^(id, NSError *) { |
| 1227 | + [expectation fulfill]; |
| 1228 | + }]; |
| 1229 | + [self awaitExpectation:expectation]; |
| 1230 | + deletedDocumentIds = [NSSet setWithArray:deletedDocumentIdsAccumulator]; |
| 1231 | + } |
| 1232 | + XCTAssertEqual(deletedDocumentIds.count, 50u, @"deletedDocumentIds has the wrong size"); |
| 1233 | + |
| 1234 | + // Wait for 10 seconds, during which Watch will stop tracking the query and will send an existence |
| 1235 | + // filter rather than "delete" events when the query is resumed. |
| 1236 | + [NSThread sleepForTimeInterval:10.0f]; |
| 1237 | + |
| 1238 | + // Resume the query and save the resulting snapshot for verification. |
| 1239 | + FIRQuerySnapshot *querySnapshot2 = [self readDocumentSetForRef:collRef |
| 1240 | + source:FIRFirestoreSourceDefault]; |
| 1241 | + |
| 1242 | + // Verify that the snapshot from the resumed query contains the expected documents; that is, |
| 1243 | + // that it contains the 50 documents that were _not_ deleted. |
| 1244 | + // TODO(b/270731363): Remove the "if" condition below once the Firestore Emulator is fixed to |
| 1245 | + // send an existence filter. At the time of writing, the Firestore emulator fails to send an |
| 1246 | + // existence filter, resulting in the client including the deleted documents in the snapshot |
| 1247 | + // of the resumed query. |
| 1248 | + if (!([FSTIntegrationTestCase isRunningAgainstEmulator] && querySnapshot2.count == 100)) { |
| 1249 | + NSSet<NSString *> *actualDocumentIds = |
| 1250 | + [NSSet setWithArray:FIRQuerySnapshotGetIDs(querySnapshot2)]; |
| 1251 | + NSSet<NSString *> *expectedDocumentIds; |
| 1252 | + { |
| 1253 | + NSMutableArray<NSString *> *expectedDocumentIdsAccumulator = [[NSMutableArray alloc] init]; |
| 1254 | + for (FIRDocumentReference *documentRef in createdDocuments) { |
| 1255 | + if (![deletedDocumentIds containsObject:documentRef.documentID]) { |
| 1256 | + [expectedDocumentIdsAccumulator addObject:documentRef.documentID]; |
| 1257 | + } |
| 1258 | + } |
| 1259 | + expectedDocumentIds = [NSSet setWithArray:expectedDocumentIdsAccumulator]; |
| 1260 | + } |
| 1261 | + if (![actualDocumentIds isEqualToSet:expectedDocumentIds]) { |
| 1262 | + NSArray<NSString *> *unexpectedDocumentIds = |
| 1263 | + SortedStringsNotIn(actualDocumentIds, expectedDocumentIds); |
| 1264 | + NSArray<NSString *> *missingDocumentIds = |
| 1265 | + SortedStringsNotIn(expectedDocumentIds, actualDocumentIds); |
| 1266 | + XCTFail(@"querySnapshot2 contained %lu documents (expected %lu): " |
| 1267 | + @"%lu unexpected and %lu missing; " |
| 1268 | + @"unexpected documents: %@; missing documents: %@", |
| 1269 | + (unsigned long)actualDocumentIds.count, (unsigned long)expectedDocumentIds.count, |
| 1270 | + (unsigned long)unexpectedDocumentIds.count, (unsigned long)missingDocumentIds.count, |
| 1271 | + [unexpectedDocumentIds componentsJoinedByString:@", "], |
| 1272 | + [missingDocumentIds componentsJoinedByString:@", "]); |
| 1273 | + } |
| 1274 | + } |
| 1275 | +} |
| 1276 | + |
1183 | 1277 | @end
|
0 commit comments