FIRQueryTests.mm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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 <FirebaseFirestore/FirebaseFirestore.h>
  17. #import <XCTest/XCTest.h>
  18. #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
  19. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  20. @interface FIRQueryTests : FSTIntegrationTestCase
  21. @end
  22. @implementation FIRQueryTests
  23. - (void)testLimitQueries {
  24. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  25. @"a" : @{@"k" : @"a"},
  26. @"b" : @{@"k" : @"b"},
  27. @"c" : @{@"k" : @"c"}
  28. }];
  29. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collRef queryLimitedTo:2]];
  30. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[ @{@"k" : @"a"}, @{@"k" : @"b"} ]));
  31. }
  32. - (void)testLimitQueriesWithDescendingSortOrder {
  33. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  34. @"a" : @{@"k" : @"a", @"sort" : @0},
  35. @"b" : @{@"k" : @"b", @"sort" : @1},
  36. @"c" : @{@"k" : @"c", @"sort" : @1},
  37. @"d" : @{@"k" : @"d", @"sort" : @2},
  38. }];
  39. FIRQuerySnapshot *snapshot =
  40. [self readDocumentSetForRef:[[collRef queryOrderedByField:@"sort"
  41. descending:YES] queryLimitedTo:2]];
  42. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot),
  43. (@[ @{@"k" : @"d", @"sort" : @2}, @{@"k" : @"c", @"sort" : @1} ]));
  44. }
  45. - (void)testKeyOrderIsDescendingForDescendingInequality {
  46. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  47. @"a" : @{@"foo" : @42},
  48. @"b" : @{@"foo" : @42.0},
  49. @"c" : @{@"foo" : @42},
  50. @"d" : @{@"foo" : @21},
  51. @"e" : @{@"foo" : @21.0},
  52. @"f" : @{@"foo" : @66},
  53. @"g" : @{@"foo" : @66.0},
  54. }];
  55. FIRQuerySnapshot *snapshot =
  56. [self readDocumentSetForRef:[[collRef queryWhereField:@"foo"
  57. isGreaterThan:@21] queryOrderedByField:@"foo"
  58. descending:YES]];
  59. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"g", @"f", @"c", @"b", @"a" ]));
  60. }
  61. - (void)testUnaryFilterQueries {
  62. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  63. @"a" : @{@"null" : [NSNull null], @"nan" : @(NAN)},
  64. @"b" : @{@"null" : [NSNull null], @"nan" : @0},
  65. @"c" : @{@"null" : @NO, @"nan" : @(NAN)}
  66. }];
  67. FIRQuerySnapshot *results =
  68. [self readDocumentSetForRef:[[collRef queryWhereField:@"null"
  69. isEqualTo:[NSNull null]] queryWhereField:@"nan"
  70. isEqualTo:@(NAN)]];
  71. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results),
  72. (@[ @{@"null" : [NSNull null], @"nan" : @(NAN)} ]));
  73. }
  74. - (void)testQueryWithFieldPaths {
  75. FIRCollectionReference *collRef = [self
  76. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  77. FIRQuery *query = [collRef queryWhereFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  78. isLessThan:@3];
  79. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  80. descending:YES];
  81. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  82. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  83. }
  84. - (void)testQueryWithPredicate {
  85. FIRCollectionReference *collRef = [self
  86. collectionRefWithDocuments:@{@"a" : @{@"a" : @1}, @"b" : @{@"a" : @2}, @"c" : @{@"a" : @3}}];
  87. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a < 3"];
  88. FIRQuery *query = [collRef queryFilteredUsingPredicate:predicate];
  89. query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
  90. descending:YES];
  91. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
  92. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
  93. }
  94. - (void)testFilterOnInfinity {
  95. FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
  96. @"a" : @{@"inf" : @(INFINITY)},
  97. @"b" : @{@"inf" : @(-INFINITY)}
  98. }];
  99. FIRQuerySnapshot *results = [self readDocumentSetForRef:[collRef queryWhereField:@"inf"
  100. isEqualTo:@(INFINITY)]];
  101. XCTAssertEqualObjects(FIRQuerySnapshotGetData(results), (@[ @{@"inf" : @(INFINITY)} ]));
  102. }
  103. - (void)testCanExplicitlySortByDocumentID {
  104. NSDictionary *testDocs = @{
  105. @"a" : @{@"key" : @"a"},
  106. @"b" : @{@"key" : @"b"},
  107. @"c" : @{@"key" : @"c"},
  108. };
  109. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  110. // Ideally this would be descending to validate it's different than
  111. // the default, but that requires an extra index
  112. FIRQuerySnapshot *docs =
  113. [self readDocumentSetForRef:[collection queryOrderedByFieldPath:[FIRFieldPath documentID]]];
  114. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs),
  115. (@[ testDocs[@"a"], testDocs[@"b"], testDocs[@"c"] ]));
  116. }
  117. - (void)testCanQueryByDocumentID {
  118. NSDictionary *testDocs = @{
  119. @"aa" : @{@"key" : @"aa"},
  120. @"ab" : @{@"key" : @"ab"},
  121. @"ba" : @{@"key" : @"ba"},
  122. @"bb" : @{@"key" : @"bb"},
  123. };
  124. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  125. FIRQuerySnapshot *docs =
  126. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  127. isEqualTo:@"ab"]];
  128. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  129. }
  130. - (void)testCanQueryByDocumentIDs {
  131. NSDictionary *testDocs = @{
  132. @"aa" : @{@"key" : @"aa"},
  133. @"ab" : @{@"key" : @"ab"},
  134. @"ba" : @{@"key" : @"ba"},
  135. @"bb" : @{@"key" : @"bb"},
  136. };
  137. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  138. FIRQuerySnapshot *docs =
  139. [self readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  140. isEqualTo:@"ab"]];
  141. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  142. docs = [self readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  143. isGreaterThan:@"aa"]
  144. queryWhereFieldPath:[FIRFieldPath documentID]
  145. isLessThanOrEqualTo:@"ba"]];
  146. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  147. }
  148. - (void)testCanQueryByDocumentIDsUsingRefs {
  149. NSDictionary *testDocs = @{
  150. @"aa" : @{@"key" : @"aa"},
  151. @"ab" : @{@"key" : @"ab"},
  152. @"ba" : @{@"key" : @"ba"},
  153. @"bb" : @{@"key" : @"bb"},
  154. };
  155. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  156. FIRQuerySnapshot *docs = [self
  157. readDocumentSetForRef:[collection queryWhereFieldPath:[FIRFieldPath documentID]
  158. isEqualTo:[collection documentWithPath:@"ab"]]];
  159. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"] ]));
  160. docs = [self
  161. readDocumentSetForRef:[[collection queryWhereFieldPath:[FIRFieldPath documentID]
  162. isGreaterThan:[collection documentWithPath:@"aa"]]
  163. queryWhereFieldPath:[FIRFieldPath documentID]
  164. isLessThanOrEqualTo:[collection documentWithPath:@"ba"]]];
  165. XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
  166. }
  167. - (void)testWatchSurvivesNetworkDisconnect {
  168. XCTestExpectation *testExpectiation =
  169. [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
  170. FIRCollectionReference *collectionRef = [self collectionRef];
  171. FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
  172. FIRFirestore *firestore = collectionRef.firestore;
  173. [collectionRef
  174. addSnapshotListenerWithIncludeMetadataChanges:YES
  175. listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
  176. XCTAssertNil(error);
  177. if (!snapshot.empty && !snapshot.metadata.fromCache) {
  178. [testExpectiation fulfill];
  179. }
  180. }];
  181. [firestore disableNetworkWithCompletion:^(NSError *error) {
  182. XCTAssertNil(error);
  183. [docRef setData:@{@"foo" : @"bar"}];
  184. [firestore enableNetworkWithCompletion:^(NSError *error) {
  185. XCTAssertNil(error);
  186. }];
  187. }];
  188. [self awaitExpectations];
  189. }
  190. - (void)testQueriesFireFromCacheWhenOffline {
  191. NSDictionary *testDocs = @{
  192. @"a" : @{@"foo" : @1},
  193. };
  194. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  195. id<FIRListenerRegistration> registration = [collection
  196. addSnapshotListenerWithIncludeMetadataChanges:YES
  197. listener:self.eventAccumulator.valueEventHandler];
  198. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  199. XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnap), @[ @{@"foo" : @1} ]);
  200. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  201. [self disableNetwork];
  202. querySnap = [self.eventAccumulator awaitEventWithName:@"offline event with isFromCache=YES"];
  203. XCTAssertEqual(querySnap.metadata.isFromCache, YES);
  204. [self enableNetwork];
  205. querySnap = [self.eventAccumulator awaitEventWithName:@"back online event with isFromCache=NO"];
  206. XCTAssertEqual(querySnap.metadata.isFromCache, NO);
  207. [registration remove];
  208. }
  209. - (void)testDocumentChangesUseNSNotFound {
  210. NSDictionary *testDocs = @{
  211. @"a" : @{@"foo" : @1},
  212. };
  213. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  214. id<FIRListenerRegistration> registration =
  215. [collection addSnapshotListener:self.eventAccumulator.valueEventHandler];
  216. FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
  217. XCTAssertEqual(querySnap.documentChanges.count, 1);
  218. FIRDocumentChange *change = querySnap.documentChanges[0];
  219. XCTAssertEqual(change.oldIndex, NSNotFound);
  220. XCTAssertEqual(change.newIndex, 0);
  221. FIRDocumentReference *doc = change.document.reference;
  222. [self deleteDocumentRef:doc];
  223. querySnap = [self.eventAccumulator awaitEventWithName:@"delete"];
  224. XCTAssertEqual(querySnap.documentChanges.count, 1);
  225. change = querySnap.documentChanges[0];
  226. XCTAssertEqual(change.oldIndex, 0);
  227. XCTAssertEqual(change.newIndex, NSNotFound);
  228. [registration remove];
  229. }
  230. - (void)testCanHaveMultipleMutationsWhileOffline {
  231. FIRCollectionReference *col = [self collectionRef];
  232. // set a few docs to known values
  233. NSDictionary *initialDocs = @{@"doc1" : @{@"key1" : @"value1"}, @"doc2" : @{@"key2" : @"value2"}};
  234. [self writeAllDocuments:initialDocs toCollection:col];
  235. // go offline for the rest of this test
  236. [self disableNetwork];
  237. // apply *multiple* mutations while offline
  238. [[col documentWithPath:@"doc1"] setData:@{@"key1b" : @"value1b"}];
  239. [[col documentWithPath:@"doc2"] setData:@{@"key2b" : @"value2b"}];
  240. FIRQuerySnapshot *result = [self readDocumentSetForRef:col];
  241. XCTAssertEqualObjects(FIRQuerySnapshotGetData(result), (@[
  242. @{@"key1b" : @"value1b"},
  243. @{@"key2b" : @"value2b"},
  244. ]));
  245. }
  246. - (void)testArrayContainsQueries {
  247. NSDictionary *testDocs = @{
  248. @"a" : @{@"array" : @[ @42 ]},
  249. @"b" : @{@"array" : @[ @"a", @42, @"c" ]},
  250. @"c" : @{@"array" : @[ @41.999, @"42", @{@"a" : @[ @42 ]} ]},
  251. @"d" : @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]}
  252. };
  253. FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
  254. // Search for 42
  255. FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:[collection queryWhereField:@"array"
  256. arrayContains:@42]];
  257. XCTAssertEqualObjects(FIRQuerySnapshotGetData(snapshot), (@[
  258. @{@"array" : @[ @42 ]}, @{@"array" : @[ @"a", @42, @"c" ]},
  259. @{@"array" : @[ @42 ], @"array2" : @[ @"bingo" ]}
  260. ]));
  261. // NOTE: The backend doesn't currently support null, NaN, objects, or arrays, so there isn't much
  262. // of anything else interesting to test.
  263. }
  264. - (void)testCollectionGroupQueries {
  265. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  266. // for predictable ordering.
  267. NSString *collectionGroup = [NSString
  268. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  269. NSArray *docPaths = @[
  270. @"abc/123/${collectionGroup}/cg-doc1", @"abc/123/${collectionGroup}/cg-doc2",
  271. @"${collectionGroup}/cg-doc3", @"${collectionGroup}/cg-doc4",
  272. @"def/456/${collectionGroup}/cg-doc5", @"${collectionGroup}/virtual-doc/nested-coll/not-cg-doc",
  273. @"x${collectionGroup}/not-cg-doc", @"${collectionGroup}x/not-cg-doc",
  274. @"abc/123/${collectionGroup}x/not-cg-doc", @"abc/123/x${collectionGroup}/not-cg-doc",
  275. @"abc/${collectionGroup}"
  276. ];
  277. FIRWriteBatch *batch = [self.db batch];
  278. for (NSString *docPath in docPaths) {
  279. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  280. withString:collectionGroup];
  281. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  282. }
  283. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  284. [batch commitWithCompletion:^(NSError *error) {
  285. XCTAssertNil(error);
  286. [expectation fulfill];
  287. }];
  288. [self awaitExpectations];
  289. FIRQuerySnapshot *querySnapshot =
  290. [self readDocumentSetForRef:[self.db collectionGroupWithID:collectionGroup]];
  291. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  292. XCTAssertEqualObjects(ids, (@[ @"cg-doc1", @"cg-doc2", @"cg-doc3", @"cg-doc4", @"cg-doc5" ]));
  293. }
  294. - (void)testCollectionGroupQueriesWithStartAtEndAtWithArbitraryDocumentIDs {
  295. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  296. // for predictable ordering.
  297. NSString *collectionGroup = [NSString
  298. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  299. NSArray *docPaths = @[
  300. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  301. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  302. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  303. ];
  304. FIRWriteBatch *batch = [self.db batch];
  305. for (NSString *docPath in docPaths) {
  306. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  307. withString:collectionGroup];
  308. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  309. }
  310. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  311. [batch commitWithCompletion:^(NSError *error) {
  312. XCTAssertNil(error);
  313. [expectation fulfill];
  314. }];
  315. [self awaitExpectations];
  316. FIRQuerySnapshot *querySnapshot = [self
  317. readDocumentSetForRef:[[[[self.db collectionGroupWithID:collectionGroup]
  318. queryOrderedByFieldPath:[FIRFieldPath documentID]]
  319. queryStartingAfterValues:@[ @"a/b" ]]
  320. queryEndingBeforeValues:@[
  321. [NSString stringWithFormat:@"a/b/%@/cg-doc3", collectionGroup]
  322. ]]];
  323. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  324. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  325. }
  326. - (void)testCollectionGroupQueriesWithWhereFiltersOnArbitraryDocumentIDs {
  327. // Use .document() to get a random collection group name to use but ensure it starts with 'b'
  328. // for predictable ordering.
  329. NSString *collectionGroup = [NSString
  330. stringWithFormat:@"b%@", [[self.db collectionWithPath:@"foo"] documentWithAutoID].documentID];
  331. NSArray *docPaths = @[
  332. @"a/a/${collectionGroup}/cg-doc1", @"a/b/a/b/${collectionGroup}/cg-doc2",
  333. @"a/b/${collectionGroup}/cg-doc3", @"a/b/c/d/${collectionGroup}/cg-doc4",
  334. @"a/c/${collectionGroup}/cg-doc5", @"${collectionGroup}/cg-doc6", @"a/b/nope/nope"
  335. ];
  336. FIRWriteBatch *batch = [self.db batch];
  337. for (NSString *docPath in docPaths) {
  338. NSString *path = [docPath stringByReplacingOccurrencesOfString:@"${collectionGroup}"
  339. withString:collectionGroup];
  340. [batch setData:@{@"x" : @1} forDocument:[self.db documentWithPath:path]];
  341. }
  342. XCTestExpectation *expectation = [self expectationWithDescription:@"batch written"];
  343. [batch commitWithCompletion:^(NSError *error) {
  344. XCTAssertNil(error);
  345. [expectation fulfill];
  346. }];
  347. [self awaitExpectations];
  348. FIRQuerySnapshot *querySnapshot = [self
  349. readDocumentSetForRef:[[[self.db collectionGroupWithID:collectionGroup]
  350. queryWhereFieldPath:[FIRFieldPath documentID]
  351. isGreaterThanOrEqualTo:@"a/b"]
  352. queryWhereFieldPath:[FIRFieldPath documentID]
  353. isLessThan:[NSString stringWithFormat:@"a/b/%@/cg-doc3",
  354. collectionGroup]]];
  355. NSArray<NSString *> *ids = FIRQuerySnapshotGetIDs(querySnapshot);
  356. XCTAssertEqualObjects(ids, (@[ @"cg-doc2" ]));
  357. }
  358. @end