FSTIntegrationTestCase.mm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. /*
  2. * Copyright 2017 Google LLC
  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/Example/Tests/Util/FSTIntegrationTestCase.h"
  17. #import <FirebaseFirestore/FIRCollectionReference.h>
  18. #import <FirebaseFirestore/FIRDocumentChange.h>
  19. #import <FirebaseFirestore/FIRDocumentReference.h>
  20. #import <FirebaseFirestore/FIRDocumentSnapshot.h>
  21. #import <FirebaseFirestore/FIRFirestore.h>
  22. #import <FirebaseFirestore/FIRFirestoreSettings.h>
  23. #import <FirebaseFirestore/FIRQuerySnapshot.h>
  24. #import <FirebaseFirestore/FIRSnapshotMetadata.h>
  25. #import <FirebaseFirestore/FIRTransaction.h>
  26. #import <FirebaseFirestore/FIRWriteBatch.h>
  27. #include <exception>
  28. #include <memory>
  29. #include <string>
  30. #include <utility>
  31. #import "FirebaseCore/Extension/FIRAppInternal.h"
  32. #import "FirebaseCore/Extension/FIRLogger.h"
  33. #import "FirebaseCore/Sources/Public/FirebaseCore/FIRLoggerLevel.h"
  34. #import "FirebaseCore/Sources/Public/FirebaseCore/FIROptions.h"
  35. #import "Firestore/Example/Tests/Util/FIRFirestore+Testing.h"
  36. #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
  37. #import "Firestore/Source/API/FIRAggregateQuery+Internal.h"
  38. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  39. #include "Firestore/core/src/credentials/credentials_provider.h"
  40. #include "Firestore/core/src/credentials/empty_credentials_provider.h"
  41. #include "Firestore/core/src/credentials/user.h"
  42. #include "Firestore/core/src/local/leveldb_opener.h"
  43. #include "Firestore/core/src/model/database_id.h"
  44. #include "Firestore/core/src/remote/firebase_metadata_provider_apple.h"
  45. #include "Firestore/core/src/remote/grpc_connection.h"
  46. #include "Firestore/core/src/util/async_queue.h"
  47. #include "Firestore/core/src/util/autoid.h"
  48. #include "Firestore/core/src/util/filesystem.h"
  49. #include "Firestore/core/src/util/path.h"
  50. #include "Firestore/core/src/util/string_apple.h"
  51. #include "Firestore/core/test/unit/testutil/app_testing.h"
  52. #include "Firestore/core/test/unit/testutil/async_testing.h"
  53. #include "Firestore/core/test/unit/testutil/status_testing.h"
  54. #include "absl/memory/memory.h"
  55. using firebase::firestore::core::DatabaseInfo;
  56. using firebase::firestore::credentials::CredentialChangeListener;
  57. using firebase::firestore::credentials::EmptyAppCheckCredentialsProvider;
  58. using firebase::firestore::credentials::EmptyAuthCredentialsProvider;
  59. using firebase::firestore::credentials::User;
  60. using firebase::firestore::local::LevelDbOpener;
  61. using firebase::firestore::model::DatabaseId;
  62. using firebase::firestore::remote::FirebaseMetadataProviderApple;
  63. using firebase::firestore::testutil::AppForUnitTesting;
  64. using firebase::firestore::testutil::AsyncQueueForTesting;
  65. using firebase::firestore::util::AsyncQueue;
  66. using firebase::firestore::util::CreateAutoId;
  67. using firebase::firestore::util::Filesystem;
  68. using firebase::firestore::util::MakeString;
  69. using firebase::firestore::util::Path;
  70. using firebase::firestore::util::Status;
  71. using firebase::firestore::util::StatusOr;
  72. NS_ASSUME_NONNULL_BEGIN
  73. /**
  74. * Firestore databases can be subject to a ~30s "cold start" delay if they have not been used
  75. * recently, so before any tests run we "prime" the backend.
  76. */
  77. static const double kPrimingTimeout = 45.0;
  78. static NSString *defaultProjectId;
  79. static NSString *defaultDatabaseId = @"(default)";
  80. static NSString *enterpriseDatabaseId = @"enterprise";
  81. static FIRFirestoreSettings *defaultSettings;
  82. static bool runningAgainstEmulator = false;
  83. // Behaves the same as `EmptyCredentialsProvider` except it can also trigger a user
  84. // change.
  85. class FakeAuthCredentialsProvider : public EmptyAuthCredentialsProvider {
  86. public:
  87. void SetCredentialChangeListener(CredentialChangeListener<User> changeListener) override {
  88. if (changeListener) {
  89. listener_ = std::move(changeListener);
  90. listener_(User::Unauthenticated());
  91. }
  92. }
  93. void ChangeUser(NSString *new_id) {
  94. if (listener_) {
  95. listener_(firebase::firestore::credentials::User::FromUid(new_id));
  96. }
  97. }
  98. private:
  99. CredentialChangeListener<User> listener_;
  100. };
  101. @implementation FSTIntegrationTestCase {
  102. NSMutableArray<FIRFirestore *> *_firestores;
  103. std::shared_ptr<EmptyAppCheckCredentialsProvider> _fakeAppCheckCredentialsProvider;
  104. std::shared_ptr<FakeAuthCredentialsProvider> _fakeAuthCredentialsProvider;
  105. }
  106. - (void)setUp {
  107. [super setUp];
  108. LoadXCTestCaseAwait();
  109. _fakeAppCheckCredentialsProvider = std::make_shared<EmptyAppCheckCredentialsProvider>();
  110. _fakeAuthCredentialsProvider = std::make_shared<FakeAuthCredentialsProvider>();
  111. [self clearPersistenceOnce];
  112. [self primeBackend];
  113. _firestores = [NSMutableArray array];
  114. self.db = [self firestore];
  115. self.eventAccumulator = [FSTEventAccumulator accumulatorForTest:self];
  116. }
  117. - (void)tearDown {
  118. @try {
  119. for (FIRFirestore *firestore in _firestores) {
  120. [self terminateFirestore:firestore];
  121. }
  122. } @finally {
  123. _firestores = nil;
  124. [super tearDown];
  125. }
  126. }
  127. /**
  128. * Clears persistence, but only the first time. This ensures that each test
  129. * run is isolated from the last test run, but doesn't allow tests to interfere
  130. * with each other.
  131. */
  132. - (void)clearPersistenceOnce {
  133. auto *fs = Filesystem::Default();
  134. static bool clearedPersistence = false;
  135. @synchronized([FSTIntegrationTestCase class]) {
  136. if (clearedPersistence) return;
  137. DatabaseInfo dbInfo;
  138. LevelDbOpener opener(dbInfo);
  139. StatusOr<Path> maybeLevelDBDir = opener.FirestoreAppDataDir();
  140. ASSERT_OK(maybeLevelDBDir.status());
  141. Path levelDBDir = std::move(maybeLevelDBDir).ValueOrDie();
  142. Status status = fs->RecursivelyRemove(levelDBDir);
  143. ASSERT_OK(status);
  144. clearedPersistence = true;
  145. }
  146. }
  147. - (FIRFirestore *)firestore {
  148. return [self firestoreWithProjectID:[FSTIntegrationTestCase projectID]];
  149. }
  150. /**
  151. * Figures out what kind of testing environment we're using, and sets up testing defaults to make
  152. * that work.
  153. *
  154. * Several configurations are supported:
  155. * * Mobile Harness, running periocally against prod and nightly, using live SSL certs
  156. * * Firestore emulator, running on localhost, with SSL disabled
  157. *
  158. * See Firestore/README.md for detailed setup instructions or comments below for which specific
  159. * values trigger which configurations.
  160. */
  161. + (void)setUpDefaults {
  162. if (defaultSettings) return;
  163. defaultSettings = [[FIRFirestoreSettings alloc] init];
  164. // Setup database id to use.
  165. NSString *databaseId = [[NSProcessInfo processInfo] environment][@"TARGET_DATABASE_ID"];
  166. if (databaseId) {
  167. defaultDatabaseId = databaseId;
  168. } else {
  169. defaultDatabaseId = @"enterprise";
  170. }
  171. // Check for a MobileHarness configuration, running against nightly or prod, which have live
  172. // SSL certs.
  173. NSString *project = [[NSProcessInfo processInfo] environment][@"PROJECT_ID"];
  174. NSString *targetBackend = [[NSProcessInfo processInfo] environment][@"TARGET_BACKEND"];
  175. NSString *host;
  176. if (targetBackend) {
  177. if ([targetBackend isEqualToString:@"emulator"]) {
  178. [self setUpEmulatorDefault];
  179. return;
  180. } else if ([targetBackend isEqualToString:@"qa"]) {
  181. host = @"staging-firestore.sandbox.googleapis.com";
  182. } else if ([targetBackend isEqualToString:@"nightly"]) {
  183. host = @"test-firestore.sandbox.googleapis.com";
  184. } else if ([targetBackend isEqualToString:@"prod"]) {
  185. host = @"firestore.googleapis.com";
  186. } else {
  187. @throw [[NSException alloc]
  188. initWithName:@"InvalidArgumentError"
  189. reason:[NSString stringWithFormat:
  190. @"Unexpected TARGET_BACKEND environment variable \"%@\"",
  191. targetBackend]
  192. userInfo:nil];
  193. }
  194. } else {
  195. host = [[NSProcessInfo processInfo] environment][@"DATASTORE_HOST"];
  196. }
  197. if (project && host) {
  198. defaultProjectId = project;
  199. defaultSettings.host = host;
  200. NSLog(@"Integration tests running against %@/(%@:%@)", defaultSettings.host, defaultProjectId,
  201. defaultDatabaseId);
  202. return;
  203. }
  204. // Check for configuration of a prod project via GoogleServices-Info.plist.
  205. FIROptions *options = [FIROptions defaultOptions];
  206. if (options && ![options.projectID isEqualToString:@"abc-xyz-123"]) {
  207. defaultProjectId = options.projectID;
  208. if (host) {
  209. // Allow access to nightly or other hosts via this mechanism too.
  210. defaultSettings.host = host;
  211. }
  212. NSLog(@"Integration tests running against %@/(%@:%@)", defaultSettings.host, defaultProjectId,
  213. defaultDatabaseId);
  214. return;
  215. }
  216. // Otherwise fall back on assuming the emulator or localhost.
  217. [self setUpEmulatorDefault];
  218. }
  219. + (void)setUpEmulatorDefault {
  220. defaultProjectId = @"test-db";
  221. defaultSettings.host = @"localhost:8080";
  222. defaultSettings.sslEnabled = false;
  223. runningAgainstEmulator = true;
  224. NSLog(@"Integration tests running against the emulator at %@/%@", defaultSettings.host,
  225. defaultProjectId);
  226. }
  227. + (NSString *)projectID {
  228. if (!defaultProjectId) {
  229. [self setUpDefaults];
  230. }
  231. return defaultProjectId;
  232. }
  233. + (NSString *)databaseID {
  234. if (!defaultDatabaseId) {
  235. return @"(default)";
  236. }
  237. return defaultDatabaseId;
  238. }
  239. + (void)switchToEnterpriseMode {
  240. defaultDatabaseId = enterpriseDatabaseId;
  241. }
  242. + (bool)isRunningAgainstEmulator {
  243. // The only way to determine whether or not we're running against the emulator is to figure out
  244. // which testing environment we're using. Essentially `setUpDefaults` determines
  245. // `runningAgainstEmulator` as a side effect.
  246. if (!defaultProjectId) {
  247. [self setUpDefaults];
  248. }
  249. return runningAgainstEmulator;
  250. }
  251. + (FIRFirestoreSettings *)settings {
  252. [self setUpDefaults];
  253. return defaultSettings;
  254. }
  255. - (FIRFirestore *)firestoreWithProjectID:(NSString *)projectID {
  256. FIRApp *app = AppForUnitTesting(MakeString(projectID));
  257. return [self firestoreWithApp:app];
  258. }
  259. - (FIRFirestore *)firestoreWithApp:(FIRApp *)app {
  260. NSString *persistenceKey = [NSString stringWithFormat:@"db%lu", (unsigned long)_firestores.count];
  261. FIRSetLoggerLevel(FIRLoggerLevelDebug);
  262. std::string projectID = MakeString(app.options.projectID);
  263. std::string databaseID = MakeString(defaultDatabaseId);
  264. FIRFirestore *firestore =
  265. [[FIRFirestore alloc] initWithDatabaseID:DatabaseId(projectID, databaseID)
  266. persistenceKey:MakeString(persistenceKey)
  267. authCredentialsProvider:_fakeAuthCredentialsProvider
  268. appCheckCredentialsProvider:_fakeAppCheckCredentialsProvider
  269. workerQueue:AsyncQueueForTesting()
  270. firebaseMetadataProvider:absl::make_unique<FirebaseMetadataProviderApple>(app)
  271. firebaseApp:app
  272. instanceRegistry:nil];
  273. firestore.settings = [FSTIntegrationTestCase settings];
  274. [_firestores addObject:firestore];
  275. return firestore;
  276. }
  277. - (void)triggerUserChangeWithUid:(NSString *)uid {
  278. _fakeAuthCredentialsProvider->ChangeUser(uid);
  279. }
  280. - (void)primeBackend {
  281. static dispatch_once_t onceToken;
  282. dispatch_once(&onceToken, ^{
  283. [FSTIntegrationTestCase setUpDefaults];
  284. if (runningAgainstEmulator) {
  285. // Priming not required against the emulator.
  286. return;
  287. }
  288. FIRFirestore *db = [self firestore];
  289. XCTestExpectation *watchInitialized =
  290. [self expectationWithDescription:@"Prime backend: Watch initialized"];
  291. __block XCTestExpectation *watchUpdateReceived;
  292. FIRDocumentReference *docRef = [db documentWithPath:[self documentPath]];
  293. id<FIRListenerRegistration> listenerRegistration =
  294. [docRef addSnapshotListener:^(FIRDocumentSnapshot *snapshot, NSError *) {
  295. if ([snapshot[@"value"] isEqual:@"done"]) {
  296. [watchUpdateReceived fulfill];
  297. } else {
  298. [watchInitialized fulfill];
  299. }
  300. }];
  301. // Wait for watch to initialize and deliver first event.
  302. [self awaitExpectation:watchInitialized];
  303. watchUpdateReceived = [self expectationWithDescription:@"Prime backend: Watch update received"];
  304. // Use a transaction to perform a write without triggering any local events.
  305. [docRef.firestore
  306. runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **) {
  307. [transaction setData:@{@"value" : @"done"} forDocument:docRef];
  308. return nil;
  309. }
  310. completion:^(id, NSError *){
  311. }];
  312. // Wait to see the write on the watch stream.
  313. [self waitForExpectationsWithTimeout:kPrimingTimeout
  314. handler:^(NSError *_Nullable expectationError) {
  315. if (expectationError) {
  316. XCTFail(@"Error waiting for prime backend: %@",
  317. expectationError);
  318. }
  319. }];
  320. [listenerRegistration remove];
  321. [self terminateFirestore:db];
  322. });
  323. }
  324. - (void)terminateFirestore:(FIRFirestore *)firestore {
  325. XCTestExpectation *expectation = [self expectationWithDescription:@"shutdown"];
  326. [firestore terminateWithCompletion:[self completionForExpectation:expectation]];
  327. [self awaitExpectation:expectation];
  328. }
  329. - (void)deleteApp:(FIRApp *)app {
  330. XCTestExpectation *expectation = [self expectationWithDescription:@"deleteApp"];
  331. [app deleteApp:^(BOOL completion) {
  332. XCTAssertTrue(completion);
  333. [expectation fulfill];
  334. }];
  335. [self awaitExpectation:expectation];
  336. }
  337. - (NSString *)documentPath {
  338. std::string autoId = CreateAutoId();
  339. return [NSString stringWithFormat:@"test-collection/%s", autoId.c_str()];
  340. }
  341. - (FIRDocumentReference *)documentRef {
  342. return [self.db documentWithPath:[self documentPath]];
  343. }
  344. - (FIRCollectionReference *)collectionRef {
  345. std::string autoId = CreateAutoId();
  346. NSString *collectionName = [NSString stringWithFormat:@"test-collection-%s", autoId.c_str()];
  347. return [self.db collectionWithPath:collectionName];
  348. }
  349. - (FIRCollectionReference *)collectionRefWithDocuments:
  350. (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)documents {
  351. FIRCollectionReference *collection = [self collectionRef];
  352. // Use a different instance to write the documents
  353. [self writeAllDocuments:documents
  354. toCollection:[[self firestore] collectionWithPath:collection.path]];
  355. return collection;
  356. }
  357. - (void)writeAllDocuments:(NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)documents
  358. toCollection:(FIRCollectionReference *)collection {
  359. NSMutableArray<XCTestExpectation *> *commits = [[NSMutableArray alloc] init];
  360. __block FIRWriteBatch *writeBatch = nil;
  361. __block int writeBatchSize = 0;
  362. [documents enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary<NSString *, id> *value,
  363. BOOL *) {
  364. if (writeBatch == nil) {
  365. writeBatch = [collection.firestore batch];
  366. }
  367. [writeBatch setData:value forDocument:[collection documentWithPath:key]];
  368. writeBatchSize++;
  369. // Write batches are capped at 500 writes. Use 400 just to be safe.
  370. if (writeBatchSize == 400) {
  371. XCTestExpectation *commitExpectation = [self expectationWithDescription:@"WriteBatch commit"];
  372. [writeBatch commitWithCompletion:^(NSError *_Nullable error) {
  373. [commitExpectation fulfill];
  374. XCTAssertNil(error, @"WriteBatch commit failed: %@", error);
  375. }];
  376. [commits addObject:commitExpectation];
  377. writeBatch = nil;
  378. writeBatchSize = 0;
  379. }
  380. }];
  381. if (writeBatch != nil) {
  382. XCTestExpectation *commitExpectation = [self expectationWithDescription:@"WriteBatch commit"];
  383. [writeBatch commitWithCompletion:^(NSError *_Nullable error) {
  384. [commitExpectation fulfill];
  385. XCTAssertNil(error, @"WriteBatch commit failed: %@", error);
  386. }];
  387. [commits addObject:commitExpectation];
  388. }
  389. for (XCTestExpectation *commitExpectation in commits) {
  390. [self awaitExpectation:commitExpectation];
  391. }
  392. }
  393. - (void)readerAndWriterOnDocumentRef:(void (^)(FIRDocumentReference *readerRef,
  394. FIRDocumentReference *writerRef))action {
  395. FIRFirestore *reader = self.db; // for clarity
  396. FIRFirestore *writer = [self firestore];
  397. NSString *path = [self documentPath];
  398. FIRDocumentReference *readerRef = [reader documentWithPath:path];
  399. FIRDocumentReference *writerRef = [writer documentWithPath:path];
  400. action(readerRef, writerRef);
  401. }
  402. - (FIRDocumentSnapshot *)readDocumentForRef:(FIRDocumentReference *)ref {
  403. return [self readDocumentForRef:ref source:FIRFirestoreSourceDefault];
  404. }
  405. - (FIRDocumentSnapshot *)readDocumentForRef:(FIRDocumentReference *)ref
  406. source:(FIRFirestoreSource)source {
  407. __block FIRDocumentSnapshot *result;
  408. XCTestExpectation *expectation = [self expectationWithDescription:@"getData"];
  409. [ref getDocumentWithSource:source
  410. completion:^(FIRDocumentSnapshot *doc, NSError *_Nullable error) {
  411. XCTAssertNil(error);
  412. result = doc;
  413. [expectation fulfill];
  414. }];
  415. [self awaitExpectation:expectation];
  416. return result;
  417. }
  418. - (FIRQuerySnapshot *)readDocumentSetForRef:(FIRQuery *)query {
  419. return [self readDocumentSetForRef:query source:FIRFirestoreSourceDefault];
  420. }
  421. - (FIRQuerySnapshot *)readDocumentSetForRef:(FIRQuery *)query source:(FIRFirestoreSource)source {
  422. if (query == nil) {
  423. XCTFail("Trying to read data from a nil query");
  424. }
  425. __block FIRQuerySnapshot *result;
  426. XCTestExpectation *expectation = [self expectationWithDescription:@"getData"];
  427. [query getDocumentsWithSource:source
  428. completion:^(FIRQuerySnapshot *documentSet, NSError *error) {
  429. XCTAssertNil(error);
  430. result = documentSet;
  431. [expectation fulfill];
  432. }];
  433. [self awaitExpectation:expectation];
  434. return result;
  435. }
  436. - (FIRDocumentSnapshot *)readSnapshotForRef:(FIRDocumentReference *)ref
  437. requireOnline:(BOOL)requireOnline {
  438. __block FIRDocumentSnapshot *result;
  439. XCTestExpectation *expectation = [self expectationWithDescription:@"listener"];
  440. id<FIRListenerRegistration> listener = [ref
  441. addSnapshotListenerWithIncludeMetadataChanges:YES
  442. listener:^(FIRDocumentSnapshot *snapshot,
  443. NSError *error) {
  444. XCTAssertNil(error);
  445. if (!requireOnline || !snapshot.metadata.fromCache) {
  446. result = snapshot;
  447. [expectation fulfill];
  448. }
  449. }];
  450. [self awaitExpectation:expectation];
  451. [listener remove];
  452. return result;
  453. }
  454. - (FIRAggregateQuerySnapshot *)readSnapshotForAggregate:(FIRAggregateQuery *)query {
  455. __block FIRAggregateQuerySnapshot *result;
  456. XCTestExpectation *expectation = [self expectationWithDescription:@"aggregate result"];
  457. [query aggregationWithSource:FIRAggregateSourceServer
  458. completion:^(FIRAggregateQuerySnapshot *snapshot, NSError *error) {
  459. XCTAssertNil(error);
  460. result = snapshot;
  461. [expectation fulfill];
  462. }];
  463. [self awaitExpectation:expectation];
  464. return result;
  465. }
  466. - (void)writeDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary<NSString *, id> *)data {
  467. XCTestExpectation *expectation = [self expectationWithDescription:@"setData"];
  468. [ref setData:data completion:[self completionForExpectation:expectation]];
  469. [self awaitExpectation:expectation];
  470. }
  471. - (void)updateDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary<id, id> *)data {
  472. XCTestExpectation *expectation = [self expectationWithDescription:@"updateData"];
  473. [ref updateData:data completion:[self completionForExpectation:expectation]];
  474. [self awaitExpectation:expectation];
  475. }
  476. - (void)deleteDocumentRef:(FIRDocumentReference *)ref {
  477. XCTestExpectation *expectation = [self expectationWithDescription:@"deleteDocument"];
  478. [ref deleteDocumentWithCompletion:[self completionForExpectation:expectation]];
  479. [self awaitExpectation:expectation];
  480. }
  481. - (FIRDocumentReference *)addDocumentRef:(FIRCollectionReference *)ref
  482. data:(NSDictionary<NSString *, id> *)data {
  483. XCTestExpectation *expectation = [self expectationWithDescription:@"addDocument"];
  484. FIRDocumentReference *doc = [ref addDocumentWithData:data
  485. completion:[self completionForExpectation:expectation]];
  486. [self awaitExpectation:expectation];
  487. return doc;
  488. }
  489. - (void)runTransaction:(FIRFirestore *)db
  490. block:(id _Nullable (^)(FIRTransaction *, NSError **error))block
  491. completion:
  492. (nullable void (^)(id _Nullable result, NSError *_Nullable error))completion {
  493. XCTestExpectation *expectation = [self expectationWithDescription:@"runTransaction"];
  494. [db runTransactionWithOptions:nil
  495. block:block
  496. completion:^(id _Nullable result, NSError *_Nullable error) {
  497. if (completion) {
  498. completion(result, error);
  499. }
  500. [expectation fulfill];
  501. }];
  502. [self awaitExpectation:expectation];
  503. }
  504. - (void)mergeDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary<NSString *, id> *)data {
  505. XCTestExpectation *expectation = [self expectationWithDescription:@"setDataWithMerge"];
  506. [ref setData:data merge:YES completion:[self completionForExpectation:expectation]];
  507. [self awaitExpectation:expectation];
  508. }
  509. - (void)mergeDocumentRef:(FIRDocumentReference *)ref
  510. data:(NSDictionary<NSString *, id> *)data
  511. fields:(NSArray<id> *)fields {
  512. XCTestExpectation *expectation = [self expectationWithDescription:@"setDataWithMerge"];
  513. [ref setData:data mergeFields:fields completion:[self completionForExpectation:expectation]];
  514. [self awaitExpectation:expectation];
  515. }
  516. - (void)commitWriteBatch:(FIRWriteBatch *)batch {
  517. XCTestExpectation *expectation = [self expectationWithDescription:@"WriteBatch commit"];
  518. [batch commitWithCompletion:^(NSError *_Nullable error) {
  519. [expectation fulfill];
  520. XCTAssertNil(error, @"WriteBatch commit should have succeeded, but it failed: %@", error);
  521. }];
  522. [self awaitExpectation:expectation];
  523. }
  524. - (void)disableNetwork {
  525. XCTestExpectation *expectation = [self expectationWithDescription:@"disableNetwork"];
  526. [self.db disableNetworkWithCompletion:[self completionForExpectation:expectation]];
  527. [self awaitExpectation:expectation];
  528. }
  529. - (void)enableNetwork {
  530. XCTestExpectation *expectation = [self expectationWithDescription:@"enableNetwork"];
  531. [self.db enableNetworkWithCompletion:[self completionForExpectation:expectation]];
  532. [self awaitExpectation:expectation];
  533. }
  534. /**
  535. * Checks that running the query while online (against the backend/emulator) results in the same
  536. * documents as running the query while offline. If `expectedDocs` is provided, it also checks
  537. * that both online and offline query result is equal to the expected documents.
  538. *
  539. * This function first performs a "get" for the entire COLLECTION from the server.
  540. * It then performs the QUERY from CACHE which, results in `executeFullCollectionScan()`
  541. * It then performs the QUERY from SERVER.
  542. * It then performs the QUERY from CACHE again, which results in `performQueryUsingRemoteKeys()`.
  543. * It then ensure that all the above QUERY results are the same.
  544. *
  545. * @param collection The collection on which the query is performed.
  546. * @param query The query to check
  547. * @param expectedDocs Ordered list of document keys that are expected to match the query
  548. */
  549. - (void)checkOnlineAndOfflineCollection:(FIRQuery *)collection
  550. query:(FIRQuery *)query
  551. matchesResult:(NSArray *)expectedDocs {
  552. // Note: Order matters. The following has to be done in the specific order:
  553. // 1- Pre-populate the cache with the entire collection.
  554. [self readDocumentSetForRef:collection source:FIRFirestoreSourceServer];
  555. // 2- This performs the query against the cache using full collection scan.
  556. FIRQuerySnapshot *docsFromCacheFullCollectionScan =
  557. [self readDocumentSetForRef:query source:FIRFirestoreSourceCache];
  558. // 3- This goes to the server (backend/emulator).
  559. FIRQuerySnapshot *docsFromServer = [self readDocumentSetForRef:query
  560. source:FIRFirestoreSourceServer];
  561. // 4- This performs the query against the cache using remote keys.
  562. FIRQuerySnapshot *docsFromCacheUsingRemoteKeys =
  563. [self readDocumentSetForRef:query source:FIRFirestoreSourceCache];
  564. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(docsFromServer),
  565. FIRQuerySnapshotGetIDs(docsFromCacheFullCollectionScan));
  566. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(docsFromServer),
  567. FIRQuerySnapshotGetIDs(docsFromCacheUsingRemoteKeys));
  568. XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(docsFromServer), expectedDocs);
  569. }
  570. - (const std::shared_ptr<AsyncQueue> &)queueForFirestore:(FIRFirestore *)firestore {
  571. return [firestore workerQueue];
  572. }
  573. - (void)waitUntil:(BOOL (^)())predicate {
  574. NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate];
  575. double waitSeconds = [self defaultExpectationWaitSeconds];
  576. while (!predicate() && ([NSDate timeIntervalSinceReferenceDate] - start < waitSeconds)) {
  577. // This waits for the next event or until the 100ms timeout is reached
  578. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
  579. beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
  580. }
  581. if (!predicate()) {
  582. XCTFail(@"Timeout");
  583. }
  584. }
  585. extern "C" NSArray<NSDictionary<NSString *, id> *> *FIRQuerySnapshotGetData(
  586. FIRQuerySnapshot *docs) {
  587. NSMutableArray<NSDictionary<NSString *, id> *> *result = [NSMutableArray array];
  588. for (FIRDocumentSnapshot *doc in docs.documents) {
  589. [result addObject:doc.data];
  590. }
  591. return result;
  592. }
  593. extern "C" NSArray<NSString *> *FIRQuerySnapshotGetIDs(FIRQuerySnapshot *docs) {
  594. NSMutableArray<NSString *> *result = [NSMutableArray array];
  595. for (FIRDocumentSnapshot *doc in docs.documents) {
  596. [result addObject:doc.documentID];
  597. }
  598. return result;
  599. }
  600. extern "C" NSArray<NSArray<id> *> *FIRQuerySnapshotGetDocChangesData(FIRQuerySnapshot *docs) {
  601. NSMutableArray<NSMutableArray<id> *> *result = [NSMutableArray array];
  602. for (FIRDocumentChange *docChange in docs.documentChanges) {
  603. NSMutableArray<id> *docChangeData = [NSMutableArray array];
  604. [docChangeData addObject:@(docChange.type)];
  605. [docChangeData addObject:docChange.document.documentID];
  606. [docChangeData addObject:docChange.document.data];
  607. [result addObject:docChangeData];
  608. }
  609. return result;
  610. }
  611. extern "C" NSArray<FIRDocumentReference *> *FIRDocumentReferenceArrayFromQuerySnapshot(
  612. FIRQuerySnapshot *docs) {
  613. NSMutableArray<FIRDocumentReference *> *documentReferenceAccumulator =
  614. [[NSMutableArray alloc] init];
  615. for (FIRDocumentSnapshot *documentSnapshot in docs.documents) {
  616. [documentReferenceAccumulator addObject:documentSnapshot.reference];
  617. }
  618. return [documentReferenceAccumulator copy];
  619. }
  620. @end
  621. NS_ASSUME_NONNULL_END