Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement unified emulator settings API proposal #5916

Merged
merged 40 commits into from Nov 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c338e99
Add header for API proposal
morganchen12 Jun 25, 2020
807d7bc
Fix some bugs and add implmentation
morganchen12 Jun 25, 2020
6e82b53
Fix header import
morganchen12 Jun 25, 2020
590f40c
Fix umbrella header
morganchen12 Jun 25, 2020
a2fe4e1
Add ways to modify settings object
morganchen12 Jun 25, 2020
cf2ca55
Remove unnecessary nodoc
morganchen12 Jun 25, 2020
eb63113
Update with new API
morganchen12 Jun 30, 2020
49aab18
Add implementations for Firestore and RTDB
morganchen12 Jul 29, 2020
1fcba8f
Revert emulator settings class
morganchen12 Jul 29, 2020
e387511
fix style
morganchen12 Jul 29, 2020
fbcc739
Remove bad import and assertion on override
morganchen12 Oct 16, 2020
5972704
Prevent database emu from being set after repo init
morganchen12 Oct 16, 2020
02b0fea
Add unified emu to functions
morganchen12 Oct 16, 2020
097583d
Add tests
morganchen12 Oct 16, 2020
048ce35
update header comment
morganchen12 Oct 16, 2020
9739564
fix test failures
morganchen12 Oct 16, 2020
15876be
remove unused variable
morganchen12 Oct 16, 2020
c9bed3b
fix database test
morganchen12 Oct 16, 2020
6cb754e
cast NSInteger to long
morganchen12 Oct 16, 2020
5186c94
fix style
morganchen12 Oct 16, 2020
48278aa
Fix functions test in travis
morganchen12 Oct 19, 2020
dc1a7db
Remove valueForKeyPath in tests
morganchen12 Oct 19, 2020
11161c2
fix style
morganchen12 Oct 19, 2020
1447510
update comments and remove explicit scheme
morganchen12 Oct 19, 2020
7adae5d
save progress
morganchen12 Oct 20, 2020
cd00920
Implement reference validation for RTDB
morganchen12 Oct 26, 2020
6dde31f
Add tests
morganchen12 Oct 26, 2020
c1f2413
Merge branch 'master' into morganchen/emu
morganchen12 Oct 26, 2020
583d704
Add changelog entries
morganchen12 Oct 26, 2020
d85a92b
Remove test logs
morganchen12 Oct 26, 2020
9dc82a6
Fix line lengths
morganchen12 Oct 27, 2020
641ee18
remove underlyingHost
morganchen12 Oct 27, 2020
f4ed678
Merge branch 'master' into morganchen/emu
morganchen12 Oct 28, 2020
1a11b25
fix functions tests
morganchen12 Oct 28, 2020
7ec50ad
fix integration warnings in functions test
morganchen12 Oct 28, 2020
5b4241c
Merge branch 'master' into morganchen/emu
morganchen12 Oct 29, 2020
6677c58
fix functions test
morganchen12 Oct 29, 2020
301f6b7
Merge branch 'master' into morganchen/emu
morganchen12 Nov 3, 2020
18e4e11
Fix infinite loop exposed in integration tests (#6912)
paulb777 Nov 6, 2020
8509125
Merge branch 'master' into morganchen/emu
morganchen12 Nov 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions FirebaseDatabase/CHANGELOG.md
@@ -1,3 +1,6 @@
# Unreleased
- [added] Made emulator connection API consistent between Auth, Database, Firestore, and Functions (#5916).

# v7.0.0
- [fixed] Disabled a deprecation warning. (#6502)

Expand Down
35 changes: 28 additions & 7 deletions FirebaseDatabase/Sources/Api/FIRDatabase.m
Expand Up @@ -153,13 +153,15 @@ - (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl {
}
FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl];
[FValidation validateFrom:@"referenceFromURL:" validURL:parsedUrl];
if (![parsedUrl.repoInfo.host isEqualToString:_repoInfo.host]) {
[NSException
raise:@"InvalidDatabaseURL"
format:
@"Invalid URL (%@) passed to getReference(). URL was expected "
"to match configured Database URL: %@",
databaseUrl, [self reference].URL];

BOOL isInvalidHost =
!parsedUrl.repoInfo.isCustomHost &&
![_repoInfo.host isEqualToString:parsedUrl.repoInfo.host];
if (isInvalidHost) {
[NSException raise:@"InvalidDatabaseURL"
format:@"Invalid URL (%@) passed to getReference(). URL "
@"was expected to match configured Database URL: %@",
databaseUrl, _repoInfo.host];
}
return [[FIRDatabaseReference alloc] initWithRepo:self.repo
path:parsedUrl.path];
Expand All @@ -173,6 +175,25 @@ - (void)purgeOutstandingWrites {
});
}

- (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port {
morganchen12 marked this conversation as resolved.
Show resolved Hide resolved
if (host.length == 0) {
[NSException raise:NSInvalidArgumentException
format:@"Cannot connect to nil or empty host."];
}
if (self.repo != nil) {
[NSException
raise:NSInternalInconsistencyException
format:@"Cannot connect to emulator after database initialization. "
@"Call useEmulator(host:port:) before creating a database "
@"reference or trying to load data."];
}
NSString *fullHost =
[NSString stringWithFormat:@"%@:%li", host, (long)port];
FRepoInfo *emulatorInfo = [[FRepoInfo alloc] initWithInfo:self.repoInfo
emulatedHost:fullHost];
self->_repoInfo = emulatorInfo;
}

morganchen12 marked this conversation as resolved.
Show resolved Hide resolved
- (void)goOnline {
[self ensureRepo];

Expand Down
2 changes: 1 addition & 1 deletion FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m
Expand Up @@ -115,7 +115,7 @@ - (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url {
format:@"The Database URL '%@' cannot be parsed. "
"Specify a valid DatabaseURL within FIRApp or from "
"your databaseForApp:URL: call.",
databaseUrl];
url];
} else if (![databaseUrl.path isEqualToString:@""] &&
![databaseUrl.path isEqualToString:@"/"]) {
[NSException
Expand Down
31 changes: 22 additions & 9 deletions FirebaseDatabase/Sources/Core/FRepoInfo.h
Expand Up @@ -16,25 +16,38 @@

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FRepoInfo : NSObject <NSCopying>

@property(nonatomic, readonly, strong) NSString *host;
@property(nonatomic, readonly, strong) NSString *namespace;
@property(nonatomic, strong) NSString *internalHost;
@property(nonatomic, readonly) bool secure;
/// The host that the database should connect to.
@property(nonatomic, readonly, copy) NSString *host;

@property(nonatomic, readonly, copy) NSString *namespace;
@property(nonatomic, readwrite, copy) NSString *internalHost;
@property(nonatomic, readonly, assign) BOOL secure;

/// Returns YES if the host is not a *.firebaseio.com host.
@property(nonatomic, readonly) BOOL isCustomHost;

- (id)initWithHost:(NSString *)host
isSecure:(bool)secure
withNamespace:(NSString *)namespace;
- (instancetype)initWithHost:(NSString *)host
isSecure:(BOOL)secure
withNamespace:(NSString *)namespace NS_DESIGNATED_INITIALIZER;

- (NSString *)connectionURLWithLastSessionID:(NSString *)lastSessionID;
- (instancetype)initWithInfo:(FRepoInfo *)info emulatedHost:(NSString *)host;

- (NSString *)connectionURLWithLastSessionID:(NSString *_Nullable)lastSessionID;
- (NSString *)connectionURL;
- (void)clearInternalHostCache;
- (BOOL)isDemoHost;
- (BOOL)isCustomHost;

- (id)copyWithZone:(NSZone *)zone;
- (id)copyWithZone:(NSZone *_Nullable)zone;
- (NSUInteger)hash;
- (BOOL)isEqual:(id)anObject;

- (instancetype)init NS_UNAVAILABLE;

@end

NS_ASSUME_NONNULL_END
65 changes: 37 additions & 28 deletions FirebaseDatabase/Sources/Core/FRepoInfo.m
Expand Up @@ -25,44 +25,53 @@ @interface FRepoInfo ()

@implementation FRepoInfo

@synthesize namespace;
@synthesize host;
@synthesize internalHost;
@synthesize secure;
@synthesize domain;

- (id)initWithHost:(NSString *)aHost
isSecure:(bool)isSecure
withNamespace:(NSString *)aNamespace {
- (instancetype)init {
[NSException
raise:@"FIRDatabaseInvalidInitializer"
format:@"Invalid initializer invoked. This is probably a bug in RTDB."];
abort();
}

- (instancetype)initWithHost:(NSString *)aHost
isSecure:(BOOL)isSecure
withNamespace:(NSString *)aNamespace {
self = [super init];
if (self) {
host = aHost;
domain =
[host containsString:@"."]
? [host
substringFromIndex:[host rangeOfString:@"."].location + 1]
: host;
secure = isSecure;
namespace = aNamespace;
_host = [aHost copy];
_domain =
[_host containsString:@"."]
? [_host
substringFromIndex:[_host rangeOfString:@"."].location +
1]
: _host;
_secure = isSecure;
_namespace = aNamespace;

// Get cached internal host if it exists
NSString *internalHostKey =
[NSString stringWithFormat:@"firebase:host:%@", self.host];
[NSString stringWithFormat:@"firebase:host:%@", _host];
NSString *cachedInternalHost = [[NSUserDefaults standardUserDefaults]
stringForKey:internalHostKey];
if (cachedInternalHost != nil) {
internalHost = cachedInternalHost;
} else {
internalHost = self.host;
internalHost = [_host copy];
}
}
return self;
}

- (instancetype)initWithInfo:(FRepoInfo *)info emulatedHost:(NSString *)host {
self = [self initWithHost:host isSecure:NO withNamespace:info.namespace];
return self;
}

- (NSString *)description {
// The namespace is encoded in the hostname, so we can just return this.
return [NSString
stringWithFormat:@"http%@://%@", (self.secure ? @"s" : @""), self.host];
stringWithFormat:@"http%@://%@", (_secure ? @"s" : @""), _host];
}

- (void)setInternalHost:(NSString *)newHost {
Expand All @@ -79,7 +88,7 @@ - (void)setInternalHost:(NSString *)newHost {
}

- (void)clearInternalHostCache {
internalHost = self.host;
self.internalHost = self.host;

// Remove the cached entry
NSString *internalHostKey =
Expand Down Expand Up @@ -120,25 +129,25 @@ - (NSString *)connectionURLWithLastSessionID:(NSString *)lastSessionID {
return url;
}

- (id)copyWithZone:(NSZone *)zone;
{
- (id)copyWithZone:(NSZone *)zone {
return self; // Immutable
}

- (NSUInteger)hash {
NSUInteger result = host.hash;
result = 31 * result + (secure ? 1 : 0);
result = 31 * result + namespace.hash;
result = 31 * result + host.hash;
NSUInteger result = _host.hash;
result = 31 * result + (_secure ? 1 : 0);
result = 31 * result + _namespace.hash;
result = 31 * result + _host.hash;
return result;
}

- (BOOL)isEqual:(id)anObject {
if (![anObject isKindOfClass:[FRepoInfo class]])
if (![anObject isKindOfClass:[FRepoInfo class]]) {
return NO;
}
FRepoInfo *other = (FRepoInfo *)anObject;
return secure == other.secure && [host isEqualToString:other.host] &&
[namespace isEqualToString:other.namespace];
return _secure == other.secure && [_host isEqualToString:other.host] &&
[_namespace isEqualToString:other.namespace];
}

@end
12 changes: 8 additions & 4 deletions FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabase.h
Expand Up @@ -31,9 +31,7 @@ NS_SWIFT_NAME(Database)

/**
* The NSObject initializer that has been marked as unavailable. Use the
* `database` method instead
*
* @return An instancetype instance
* `database` class method instead.
*/
- (instancetype)init
__attribute__((unavailable("use the database method instead")));
Expand Down Expand Up @@ -93,7 +91,7 @@ NS_SWIFT_NAME(Database)
/**
* Gets a FIRDatabaseReference for the provided URL. The URL must be a URL to a
* path within this Firebase Database. To create a FIRDatabaseReference to a
* different database, create a FIRApp} with a FIROptions object configured with
* different database, create a FIRApp with a FIROptions object configured with
* the appropriate database URL.
*
* @param databaseUrl A URL to a path within your database.
Expand Down Expand Up @@ -177,6 +175,12 @@ NS_SWIFT_NAME(Database)
/** Retrieve the Firebase Database SDK version. */
+ (NSString *)sdkVersion;

/**
* Configures the database to use an emulated backend instead of the default
* remote backend.
*/
- (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port;

@end

NS_ASSUME_NONNULL_END
70 changes: 62 additions & 8 deletions FirebaseDatabase/Tests/Integration/FIRDatabaseTests.m
Expand Up @@ -37,10 +37,8 @@ @interface FIRDatabaseTests : FTestBase
@implementation FIRDatabaseTests

- (void)testFIRDatabaseForNilApp {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
XCTAssertThrowsSpecificNamed([FIRDatabase databaseForApp:nil], NSException, @"InvalidFIRApp");
#pragma clang diagnostic pop
XCTAssertThrowsSpecificNamed([FIRDatabase databaseForApp:(FIRApp * _Nonnull) nil], NSException,
@"InvalidFIRApp");
}

- (void)testDatabaseForApp {
Expand Down Expand Up @@ -94,10 +92,7 @@ - (void)testDifferentInstanceForAppWithURL {
- (void)testDatabaseForAppWithInvalidCustomURLs {
id app = [[FIRFakeApp alloc] initWithName:@"testDatabaseForAppWithInvalidCustomURLs"
URL:kFirebaseTestAltNamespace];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
XCTAssertThrows([FIRDatabase databaseForApp:app URL:nil]);
#pragma clang diagnostic pop
XCTAssertThrows([FIRDatabase databaseForApp:app URL:(NSString * _Nonnull) nil]);
morganchen12 marked this conversation as resolved.
Show resolved Hide resolved
XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"not-a-url"]);
XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"http://x.fblocal.com:9000/paths/are/bad"]);
}
Expand Down Expand Up @@ -469,6 +464,65 @@ - (void)testCallbackQueue {
[database goOffline];
}

- (void)testSetEmulatorSettingsCreatesEmulatedReferences {
id app = [[FIRFakeApp alloc] initWithName:@"testSetEmulatorSettingsCreatesEmulatedReferences"
URL:self.databaseURL];
FIRDatabase *database = [FIRDatabase databaseForApp:app];

[database useEmulatorWithHost:@"localhost" port:1111];
NSString *concatenatedHost = @"localhost:1111";

FIRDatabaseReference *reference = [database reference];

NSString *referenceURLString = reference.URL;

XCTAssert([referenceURLString containsString:concatenatedHost]);
}

- (void)testSetEmulatorSettingsThrowsAfterRepoInit {
id app = [[FIRFakeApp alloc] initWithName:@"testSetEmulatorSettingsThrowsAfterRepoInit"
URL:self.databaseURL];
FIRDatabase *database = [FIRDatabase databaseForApp:app];

[database reference]; // initialize database repo

// Emulator can't be set after initialization of the database's repo.
XCTAssertThrows([database useEmulatorWithHost:@"a" port:1]);
}

- (void)testEmulatedDatabaseValidatesOnlyNonCustomURLs {
// Set a non-custom databaseURL
NSString *databaseURL = @"https://test.example.com";
id app = [[FIRFakeApp alloc] initWithName:@"testEmulatedDatabaseValidatesNonCustomURLs0"
URL:databaseURL];
FIRDatabase *database = [FIRDatabase databaseForApp:app];

// Reference should be retrievable without an exception being raised
NSString *referenceURLString = [databaseURL stringByAppendingString:@"/path"];
FIRDatabaseReference *reference = [database referenceFromURL:referenceURLString];
XCTAssertNotNil(reference);

app = [[FIRFakeApp alloc] initWithName:@"testEmulatedDatabaseValidatesNonCustomURLs1"
URL:databaseURL];
database = [FIRDatabase databaseForApp:app];
[database useEmulatorWithHost:@"localhost" port:1111];

// Expect production url creates a valid (emulated) reference.
reference = [database referenceFromURL:referenceURLString];
XCTAssertNotNil(reference);
XCTAssert([reference.URL containsString:@"localhost:1111"]);

// Test emulated url
referenceURLString = @"http://localhost:1111/path";
reference = [database referenceFromURL:referenceURLString];
XCTAssertNotNil(reference);
XCTAssert([reference.URL containsString:@"localhost:1111"]);

// Test non-custom url with different host throws exception
referenceURLString = @"https://test.firebaseio.com/path";
XCTAssertThrows([database referenceFromURL:referenceURLString]);
morganchen12 marked this conversation as resolved.
Show resolved Hide resolved
}

- (FIRDatabase *)defaultDatabase {
return [self databaseForURL:self.databaseURL];
}
Expand Down
3 changes: 3 additions & 0 deletions Firestore/CHANGELOG.md
@@ -1,3 +1,6 @@
# Unreleased
- [added] Made emulator connection API consistent between Auth, Database, Firestore, and Functions (#5916).

# v7.1.0
- [changed] Added the original query data to error messages for Queries that
cannot be deserizialized.
Expand Down
13 changes: 13 additions & 0 deletions Firestore/Example/Tests/API/FIRFirestoreTests.mm
Expand Up @@ -15,6 +15,7 @@
*/

#import <FirebaseFirestore/FIRFirestore.h>
#import <FirebaseFirestore/FIRFirestoreSettings.h>

#import <XCTest/XCTest.h>

Expand Down Expand Up @@ -63,4 +64,16 @@ - (void)testDeleteApp {
}];
}

- (void)testSetEmulatorSettingsSetsHost {
// Ensure the app is set appropriately.
FIRApp *app = testutil::AppForUnitTesting();

FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];

[firestore useEmulatorWithHost:@"localhost" port:1000];

NSString *host = firestore.settings.host;
XCTAssertEqualObjects(host, @"localhost:1000");
}

@end