mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
320 lines
12 KiB
Plaintext
320 lines
12 KiB
Plaintext
/*
|
|
* 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 <FirebaseFirestore/FirebaseFirestore.h>
|
|
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#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<XCTestExpectation *> *writeEventExpectations;
|
|
@property(nonatomic, strong) NSMutableArray<XCTestExpectation *> *listenEventExpectations;
|
|
@end
|
|
|
|
@implementation FSTRemoteStoreEventCapture {
|
|
std::vector<RemoteEvent> _listenEvents;
|
|
std::vector<MutationBatchResult> _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<AsyncQueue> _testWorkerQueue;
|
|
std::unique_ptr<LocalStore> _localStore;
|
|
std::unique_ptr<Persistence> _persistence;
|
|
|
|
DatabaseInfo _databaseInfo;
|
|
QueryEngine _queryEngine;
|
|
|
|
std::unique_ptr<ConnectivityMonitor> _connectivityMonitor;
|
|
std::unique_ptr<FirebaseMetadataProvider> _firebaseMetadataProvider;
|
|
std::shared_ptr<Datastore> _datastore;
|
|
std::unique_ptr<RemoteStore> _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<Datastore>(
|
|
_databaseInfo, _testWorkerQueue, std::make_shared<EmptyAuthCredentialsProvider>(),
|
|
std::make_shared<EmptyAppCheckCredentialsProvider>(), _connectivityMonitor.get(),
|
|
_firebaseMetadataProvider.get());
|
|
|
|
_persistence = MemoryPersistence::WithEagerGarbageCollector();
|
|
_localStore =
|
|
absl::make_unique<LocalStore>(_persistence.get(), &_queryEngine, User::Unauthenticated());
|
|
|
|
_remoteStore = absl::make_unique<RemoteStore>(_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
|