/* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import #import #include #include #import "FirebaseCore/Sources/Public/FirebaseCore/FIRTimestamp.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FSTUserDataReader.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" #include "Firestore/core/src/core/database_info.h" #include "Firestore/core/src/credentials/empty_credentials_provider.h" #include "Firestore/core/src/local/local_documents_view.h" #include "Firestore/core/src/local/local_store.h" #include "Firestore/core/src/local/memory_persistence.h" #include "Firestore/core/src/local/query_engine.h" #include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/database_id.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/mutation_batch_result.h" #include "Firestore/core/src/model/precondition.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/remote/connectivity_monitor.h" #include "Firestore/core/src/remote/datastore.h" #include "Firestore/core/src/remote/firebase_metadata_provider.h" #include "Firestore/core/src/remote/firebase_metadata_provider_noop.h" #include "Firestore/core/src/remote/remote_event.h" #include "Firestore/core/src/remote/remote_store.h" #include "Firestore/core/src/util/async_queue.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/status.h" #include "Firestore/core/src/util/string_apple.h" #include "Firestore/core/test/unit/remote/create_noop_connectivity_monitor.h" #include "Firestore/core/test/unit/testutil/async_testing.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "absl/memory/memory.h" using firebase::Timestamp; using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::core::DatabaseInfo; using firebase::firestore::credentials::EmptyAppCheckCredentialsProvider; using firebase::firestore::credentials::EmptyAuthCredentialsProvider; using firebase::firestore::credentials::User; using firebase::firestore::local::LocalStore; using firebase::firestore::local::MemoryPersistence; using firebase::firestore::local::Persistence; using firebase::firestore::local::QueryEngine; using firebase::firestore::model::BatchId; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DocumentKeySet; using firebase::firestore::model::MutationBatch; using firebase::firestore::model::MutationBatchResult; using firebase::firestore::model::OnlineState; using firebase::firestore::model::TargetId; using firebase::firestore::remote::ConnectivityMonitor; using firebase::firestore::remote::CreateFirebaseMetadataProviderNoOp; using firebase::firestore::remote::CreateNoOpConnectivityMonitor; using firebase::firestore::remote::Datastore; using firebase::firestore::remote::FirebaseMetadataProvider; using firebase::firestore::remote::GrpcConnection; using firebase::firestore::remote::RemoteEvent; using firebase::firestore::remote::RemoteStore; using firebase::firestore::remote::RemoteStoreCallback; using firebase::firestore::testutil::AsyncQueueForTesting; using firebase::firestore::testutil::Map; using firebase::firestore::testutil::SetMutation; using firebase::firestore::util::AsyncQueue; using firebase::firestore::util::MakeString; using firebase::firestore::util::Status; NS_ASSUME_NONNULL_BEGIN #pragma mark - FSTRemoteStoreEventCapture @interface FSTRemoteStoreEventCapture : NSObject - (instancetype)init __attribute__((unavailable("Use initWithTestCase:"))); - (instancetype)initWithTestCase:(XCTestCase *_Nullable)testCase NS_DESIGNATED_INITIALIZER; - (void)expectWriteEventWithDescription:(NSString *)description; - (void)expectListenEventWithDescription:(NSString *)description; @property(nonatomic, weak, nullable) XCTestCase *testCase; @property(nonatomic, strong) NSMutableArray *writeEventExpectations; @property(nonatomic, strong) NSMutableArray *listenEventExpectations; @end @implementation FSTRemoteStoreEventCapture { std::vector _listenEvents; std::vector _writeEvents; } - (instancetype)initWithTestCase:(XCTestCase *_Nullable)testCase { if (self = [super init]) { _testCase = testCase; _writeEventExpectations = [NSMutableArray array]; _listenEventExpectations = [NSMutableArray array]; } return self; } - (void)expectWriteEventWithDescription:(NSString *)description { [self.writeEventExpectations addObject:[self.testCase expectationWithDescription:[NSString stringWithFormat:@"write event %lu: %@", (unsigned long) self.writeEventExpectations .count, description]]]; } - (void)expectListenEventWithDescription:(NSString *)description { [self.listenEventExpectations addObject:[self.testCase expectationWithDescription:[NSString stringWithFormat:@"listen event %lu: %@", (unsigned long) self.listenEventExpectations .count, description]]]; } - (void)applySuccessfulWriteWithResult:(MutationBatchResult)batchResult { _writeEvents.push_back(std::move(batchResult)); XCTestExpectation *expectation = [self.writeEventExpectations objectAtIndex:0]; [self.writeEventExpectations removeObjectAtIndex:0]; [expectation fulfill]; } - (void)rejectFailedWriteWithBatchID:(__unused BatchId)batchID error:(__unused NSError *)error { HARD_FAIL("Not implemented"); } - (DocumentKeySet)remoteKeysForTarget:(__unused TargetId)targetId { return DocumentKeySet{}; } - (void)applyRemoteEvent:(const RemoteEvent &)remoteEvent { _listenEvents.push_back(remoteEvent); XCTestExpectation *expectation = [self.listenEventExpectations objectAtIndex:0]; [self.listenEventExpectations removeObjectAtIndex:0]; [expectation fulfill]; } - (void)rejectListenWithTargetID:(__unused const TargetId)targetID error:(__unused NSError *)error { HARD_FAIL("Not implemented"); } @end class RemoteStoreEventCapture : public RemoteStoreCallback { public: explicit RemoteStoreEventCapture(XCTestCase *test_case) : underlying_capture_([[FSTRemoteStoreEventCapture alloc] initWithTestCase:test_case]) { } void ExpectWriteEvent(NSString *description) { [underlying_capture_ expectWriteEventWithDescription:description]; } void ExpectListenEvent(NSString *description) { [underlying_capture_ expectListenEventWithDescription:description]; } void ApplyRemoteEvent(const RemoteEvent &remote_event) override { [underlying_capture_ applyRemoteEvent:remote_event]; } void HandleRejectedListen(TargetId target_id, Status error) override { [underlying_capture_ rejectListenWithTargetID:target_id error:error.ToNSError()]; } void HandleSuccessfulWrite(MutationBatchResult batch_result) override { [underlying_capture_ applySuccessfulWriteWithResult:std::move(batch_result)]; } void HandleRejectedWrite(BatchId batch_id, Status error) override { [underlying_capture_ rejectFailedWriteWithBatchID:batch_id error:error.ToNSError()]; } void HandleOnlineStateChange(OnlineState) override { HARD_FAIL("Not implemented"); } model::DocumentKeySet GetRemoteKeys(TargetId target_id) const override { return [underlying_capture_ remoteKeysForTarget:target_id]; } private: FSTRemoteStoreEventCapture *underlying_capture_; }; #pragma mark - FSTDatastoreTests @interface FSTDatastoreTests : XCTestCase @end @implementation FSTDatastoreTests { std::shared_ptr _testWorkerQueue; std::unique_ptr _localStore; std::unique_ptr _persistence; DatabaseInfo _databaseInfo; QueryEngine _queryEngine; std::unique_ptr _connectivityMonitor; std::unique_ptr _firebaseMetadataProvider; std::shared_ptr _datastore; std::unique_ptr _remoteStore; } - (void)setUp { [super setUp]; NSString *projectID = [FSTIntegrationTestCase projectID]; FIRFirestoreSettings *settings = [FSTIntegrationTestCase settings]; if (!settings.sslEnabled) { GrpcConnection::UseInsecureChannel(MakeString(settings.host)); } DatabaseId database_id(MakeString(projectID)); _databaseInfo = DatabaseInfo(database_id, "test-key", MakeString(settings.host), settings.sslEnabled); _testWorkerQueue = AsyncQueueForTesting(); _connectivityMonitor = CreateNoOpConnectivityMonitor(); _firebaseMetadataProvider = CreateFirebaseMetadataProviderNoOp(); _datastore = std::make_shared( _databaseInfo, _testWorkerQueue, std::make_shared(), std::make_shared(), _connectivityMonitor.get(), _firebaseMetadataProvider.get()); _persistence = MemoryPersistence::WithEagerGarbageCollector(); _localStore = absl::make_unique(_persistence.get(), &_queryEngine, User::Unauthenticated()); _remoteStore = absl::make_unique(_localStore.get(), _datastore, _testWorkerQueue, _connectivityMonitor.get(), [](OnlineState) {}); _testWorkerQueue->Enqueue([=] { _remoteStore->Start(); }); } - (void)tearDown { XCTestExpectation *completion = [self expectationWithDescription:@"shutdown"]; _testWorkerQueue->Enqueue([=] { _remoteStore->Shutdown(); [completion fulfill]; }); [self awaitExpectations]; [super tearDown]; } - (void)testCommit { XCTestExpectation *expectation = [self expectationWithDescription:@"commitWithCompletion"]; _datastore->CommitMutations({}, [self, expectation](const Status &status) { (void)self; // Avoid unused lambda capture error in Xcode 12. XCTAssertTrue(status.ok(), @"Failed to commit"); [expectation fulfill]; }); [self awaitExpectations]; } - (void)testStreamingWrite { RemoteStoreEventCapture capture(self); capture.ExpectWriteEvent(@"write mutations"); _remoteStore->set_sync_engine(&capture); auto mutation = SetMutation("rooms/eros", Map("name", "Eros")); MutationBatch batch = MutationBatch(23, Timestamp::Now(), {}, {mutation}); _testWorkerQueue->Enqueue([=] { _remoteStore->AddToWritePipeline(batch); // The added batch won't be written immediately because write stream wasn't yet open -- // trigger its opening. _remoteStore->FillWritePipeline(); }); [self awaitExpectations]; } - (void)awaitExpectations { [self waitForExpectationsWithTimeout:4.0 handler:^(NSError *_Nullable expectationError) { if (expectationError) { XCTFail(@"Error waiting for timeout: %@", expectationError); } }]; } @end NS_ASSUME_NONNULL_END