Skip to content

Commit 90d3600

Browse files
Implement unified emulator settings API proposal (#5916)
* Add header for API proposal * Fix some bugs and add implmentation * Fix header import * Fix umbrella header * Add ways to modify settings object * Remove unnecessary nodoc * Update with new API * Add implementations for Firestore and RTDB * Revert emulator settings class * fix style * Remove bad import and assertion on override * Prevent database emu from being set after repo init * Add unified emu to functions * Add tests * update header comment * fix test failures * remove unused variable * fix database test * cast NSInteger to long * fix style * Fix functions test in travis * Remove valueForKeyPath in tests * fix style * update comments and remove explicit scheme * save progress * Implement reference validation for RTDB * Add tests * Add changelog entries * Remove test logs * Fix line lengths * remove underlyingHost * fix functions tests * fix integration warnings in functions test * fix functions test * Fix infinite loop exposed in integration tests (#6912) Co-authored-by: Paul Beusterien <[email protected]>
1 parent 24cff3d commit 90d3600

File tree

17 files changed

+261
-70
lines changed

17 files changed

+261
-70
lines changed

FirebaseDatabase/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [added] Made emulator connection API consistent between Auth, Database, Firestore, and Functions (#5916).
3+
14
# v7.0.0
25
- [fixed] Disabled a deprecation warning. (#6502)
36

FirebaseDatabase/Sources/Api/FIRDatabase.m

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,15 @@ - (FIRDatabaseReference *)referenceFromURL:(NSString *)databaseUrl {
153153
}
154154
FParsedUrl *parsedUrl = [FUtilities parseUrl:databaseUrl];
155155
[FValidation validateFrom:@"referenceFromURL:" validURL:parsedUrl];
156-
if (![parsedUrl.repoInfo.host isEqualToString:_repoInfo.host]) {
157-
[NSException
158-
raise:@"InvalidDatabaseURL"
159-
format:
160-
@"Invalid URL (%@) passed to getReference(). URL was expected "
161-
"to match configured Database URL: %@",
162-
databaseUrl, [self reference].URL];
156+
157+
BOOL isInvalidHost =
158+
!parsedUrl.repoInfo.isCustomHost &&
159+
![_repoInfo.host isEqualToString:parsedUrl.repoInfo.host];
160+
if (isInvalidHost) {
161+
[NSException raise:@"InvalidDatabaseURL"
162+
format:@"Invalid URL (%@) passed to getReference(). URL "
163+
@"was expected to match configured Database URL: %@",
164+
databaseUrl, _repoInfo.host];
163165
}
164166
return [[FIRDatabaseReference alloc] initWithRepo:self.repo
165167
path:parsedUrl.path];
@@ -173,6 +175,25 @@ - (void)purgeOutstandingWrites {
173175
});
174176
}
175177

178+
- (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port {
179+
if (host.length == 0) {
180+
[NSException raise:NSInvalidArgumentException
181+
format:@"Cannot connect to nil or empty host."];
182+
}
183+
if (self.repo != nil) {
184+
[NSException
185+
raise:NSInternalInconsistencyException
186+
format:@"Cannot connect to emulator after database initialization. "
187+
@"Call useEmulator(host:port:) before creating a database "
188+
@"reference or trying to load data."];
189+
}
190+
NSString *fullHost =
191+
[NSString stringWithFormat:@"%@:%li", host, (long)port];
192+
FRepoInfo *emulatorInfo = [[FRepoInfo alloc] initWithInfo:self.repoInfo
193+
emulatedHost:fullHost];
194+
self->_repoInfo = emulatorInfo;
195+
}
196+
176197
- (void)goOnline {
177198
[self ensureRepo];
178199

FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ - (FIRDatabase *)databaseForApp:(FIRApp *)app URL:(NSString *)url {
115115
format:@"The Database URL '%@' cannot be parsed. "
116116
"Specify a valid DatabaseURL within FIRApp or from "
117117
"your databaseForApp:URL: call.",
118-
databaseUrl];
118+
url];
119119
} else if (![databaseUrl.path isEqualToString:@""] &&
120120
![databaseUrl.path isEqualToString:@"/"]) {
121121
[NSException

FirebaseDatabase/Sources/Core/FRepoInfo.h

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,38 @@
1616

1717
#import <Foundation/Foundation.h>
1818

19+
NS_ASSUME_NONNULL_BEGIN
20+
1921
@interface FRepoInfo : NSObject <NSCopying>
2022

21-
@property(nonatomic, readonly, strong) NSString *host;
22-
@property(nonatomic, readonly, strong) NSString *namespace;
23-
@property(nonatomic, strong) NSString *internalHost;
24-
@property(nonatomic, readonly) bool secure;
23+
/// The host that the database should connect to.
24+
@property(nonatomic, readonly, copy) NSString *host;
25+
26+
@property(nonatomic, readonly, copy) NSString *namespace;
27+
@property(nonatomic, readwrite, copy) NSString *internalHost;
28+
@property(nonatomic, readonly, assign) BOOL secure;
29+
30+
/// Returns YES if the host is not a *.firebaseio.com host.
31+
@property(nonatomic, readonly) BOOL isCustomHost;
2532

26-
- (id)initWithHost:(NSString *)host
27-
isSecure:(bool)secure
28-
withNamespace:(NSString *)namespace;
33+
- (instancetype)initWithHost:(NSString *)host
34+
isSecure:(BOOL)secure
35+
withNamespace:(NSString *)namespace NS_DESIGNATED_INITIALIZER;
2936

30-
- (NSString *)connectionURLWithLastSessionID:(NSString *)lastSessionID;
37+
- (instancetype)initWithInfo:(FRepoInfo *)info emulatedHost:(NSString *)host;
38+
39+
- (NSString *)connectionURLWithLastSessionID:(NSString *_Nullable)lastSessionID;
3140
- (NSString *)connectionURL;
3241
- (void)clearInternalHostCache;
3342
- (BOOL)isDemoHost;
3443
- (BOOL)isCustomHost;
3544

36-
- (id)copyWithZone:(NSZone *)zone;
45+
- (id)copyWithZone:(NSZone *_Nullable)zone;
3746
- (NSUInteger)hash;
3847
- (BOOL)isEqual:(id)anObject;
3948

49+
- (instancetype)init NS_UNAVAILABLE;
50+
4051
@end
52+
53+
NS_ASSUME_NONNULL_END

FirebaseDatabase/Sources/Core/FRepoInfo.m

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,53 @@ @interface FRepoInfo ()
2525

2626
@implementation FRepoInfo
2727

28-
@synthesize namespace;
29-
@synthesize host;
3028
@synthesize internalHost;
31-
@synthesize secure;
32-
@synthesize domain;
3329

34-
- (id)initWithHost:(NSString *)aHost
35-
isSecure:(bool)isSecure
36-
withNamespace:(NSString *)aNamespace {
30+
- (instancetype)init {
31+
[NSException
32+
raise:@"FIRDatabaseInvalidInitializer"
33+
format:@"Invalid initializer invoked. This is probably a bug in RTDB."];
34+
abort();
35+
}
36+
37+
- (instancetype)initWithHost:(NSString *)aHost
38+
isSecure:(BOOL)isSecure
39+
withNamespace:(NSString *)aNamespace {
3740
self = [super init];
3841
if (self) {
39-
host = aHost;
40-
domain =
41-
[host containsString:@"."]
42-
? [host
43-
substringFromIndex:[host rangeOfString:@"."].location + 1]
44-
: host;
45-
secure = isSecure;
46-
namespace = aNamespace;
42+
_host = [aHost copy];
43+
_domain =
44+
[_host containsString:@"."]
45+
? [_host
46+
substringFromIndex:[_host rangeOfString:@"."].location +
47+
1]
48+
: _host;
49+
_secure = isSecure;
50+
_namespace = aNamespace;
4751

4852
// Get cached internal host if it exists
4953
NSString *internalHostKey =
50-
[NSString stringWithFormat:@"firebase:host:%@", self.host];
54+
[NSString stringWithFormat:@"firebase:host:%@", _host];
5155
NSString *cachedInternalHost = [[NSUserDefaults standardUserDefaults]
5256
stringForKey:internalHostKey];
5357
if (cachedInternalHost != nil) {
5458
internalHost = cachedInternalHost;
5559
} else {
56-
internalHost = self.host;
60+
internalHost = [_host copy];
5761
}
5862
}
5963
return self;
6064
}
6165

66+
- (instancetype)initWithInfo:(FRepoInfo *)info emulatedHost:(NSString *)host {
67+
self = [self initWithHost:host isSecure:NO withNamespace:info.namespace];
68+
return self;
69+
}
70+
6271
- (NSString *)description {
6372
// The namespace is encoded in the hostname, so we can just return this.
6473
return [NSString
65-
stringWithFormat:@"http%@://%@", (self.secure ? @"s" : @""), self.host];
74+
stringWithFormat:@"http%@://%@", (_secure ? @"s" : @""), _host];
6675
}
6776

6877
- (void)setInternalHost:(NSString *)newHost {
@@ -79,7 +88,7 @@ - (void)setInternalHost:(NSString *)newHost {
7988
}
8089

8190
- (void)clearInternalHostCache {
82-
internalHost = self.host;
91+
self.internalHost = self.host;
8392

8493
// Remove the cached entry
8594
NSString *internalHostKey =
@@ -120,25 +129,25 @@ - (NSString *)connectionURLWithLastSessionID:(NSString *)lastSessionID {
120129
return url;
121130
}
122131

123-
- (id)copyWithZone:(NSZone *)zone;
124-
{
132+
- (id)copyWithZone:(NSZone *)zone {
125133
return self; // Immutable
126134
}
127135

128136
- (NSUInteger)hash {
129-
NSUInteger result = host.hash;
130-
result = 31 * result + (secure ? 1 : 0);
131-
result = 31 * result + namespace.hash;
132-
result = 31 * result + host.hash;
137+
NSUInteger result = _host.hash;
138+
result = 31 * result + (_secure ? 1 : 0);
139+
result = 31 * result + _namespace.hash;
140+
result = 31 * result + _host.hash;
133141
return result;
134142
}
135143

136144
- (BOOL)isEqual:(id)anObject {
137-
if (![anObject isKindOfClass:[FRepoInfo class]])
145+
if (![anObject isKindOfClass:[FRepoInfo class]]) {
138146
return NO;
147+
}
139148
FRepoInfo *other = (FRepoInfo *)anObject;
140-
return secure == other.secure && [host isEqualToString:other.host] &&
141-
[namespace isEqualToString:other.namespace];
149+
return _secure == other.secure && [_host isEqualToString:other.host] &&
150+
[_namespace isEqualToString:other.namespace];
142151
}
143152

144153
@end

FirebaseDatabase/Sources/Public/FirebaseDatabase/FIRDatabase.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ NS_SWIFT_NAME(Database)
3131

3232
/**
3333
* The NSObject initializer that has been marked as unavailable. Use the
34-
* `database` method instead
35-
*
36-
* @return An instancetype instance
34+
* `database` class method instead.
3735
*/
3836
- (instancetype)init
3937
__attribute__((unavailable("use the database method instead")));
@@ -93,7 +91,7 @@ NS_SWIFT_NAME(Database)
9391
/**
9492
* Gets a FIRDatabaseReference for the provided URL. The URL must be a URL to a
9593
* path within this Firebase Database. To create a FIRDatabaseReference to a
96-
* different database, create a FIRApp} with a FIROptions object configured with
94+
* different database, create a FIRApp with a FIROptions object configured with
9795
* the appropriate database URL.
9896
*
9997
* @param databaseUrl A URL to a path within your database.
@@ -177,6 +175,12 @@ NS_SWIFT_NAME(Database)
177175
/** Retrieve the Firebase Database SDK version. */
178176
+ (NSString *)sdkVersion;
179177

178+
/**
179+
* Configures the database to use an emulated backend instead of the default
180+
* remote backend.
181+
*/
182+
- (void)useEmulatorWithHost:(NSString *)host port:(NSInteger)port;
183+
180184
@end
181185

182186
NS_ASSUME_NONNULL_END

FirebaseDatabase/Tests/Integration/FIRDatabaseTests.m

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,8 @@ @interface FIRDatabaseTests : FTestBase
3737
@implementation FIRDatabaseTests
3838

3939
- (void)testFIRDatabaseForNilApp {
40-
#pragma clang diagnostic push
41-
#pragma clang diagnostic ignored "-Wnonnull"
42-
XCTAssertThrowsSpecificNamed([FIRDatabase databaseForApp:nil], NSException, @"InvalidFIRApp");
43-
#pragma clang diagnostic pop
40+
XCTAssertThrowsSpecificNamed([FIRDatabase databaseForApp:(FIRApp * _Nonnull) nil], NSException,
41+
@"InvalidFIRApp");
4442
}
4543

4644
- (void)testDatabaseForApp {
@@ -94,10 +92,7 @@ - (void)testDifferentInstanceForAppWithURL {
9492
- (void)testDatabaseForAppWithInvalidCustomURLs {
9593
id app = [[FIRFakeApp alloc] initWithName:@"testDatabaseForAppWithInvalidCustomURLs"
9694
URL:kFirebaseTestAltNamespace];
97-
#pragma clang diagnostic push
98-
#pragma clang diagnostic ignored "-Wnonnull"
99-
XCTAssertThrows([FIRDatabase databaseForApp:app URL:nil]);
100-
#pragma clang diagnostic pop
95+
XCTAssertThrows([FIRDatabase databaseForApp:app URL:(NSString * _Nonnull) nil]);
10196
XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"not-a-url"]);
10297
XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"http://x.fblocal.com:9000/paths/are/bad"]);
10398
}
@@ -469,6 +464,65 @@ - (void)testCallbackQueue {
469464
[database goOffline];
470465
}
471466

467+
- (void)testSetEmulatorSettingsCreatesEmulatedReferences {
468+
id app = [[FIRFakeApp alloc] initWithName:@"testSetEmulatorSettingsCreatesEmulatedReferences"
469+
URL:self.databaseURL];
470+
FIRDatabase *database = [FIRDatabase databaseForApp:app];
471+
472+
[database useEmulatorWithHost:@"localhost" port:1111];
473+
NSString *concatenatedHost = @"localhost:1111";
474+
475+
FIRDatabaseReference *reference = [database reference];
476+
477+
NSString *referenceURLString = reference.URL;
478+
479+
XCTAssert([referenceURLString containsString:concatenatedHost]);
480+
}
481+
482+
- (void)testSetEmulatorSettingsThrowsAfterRepoInit {
483+
id app = [[FIRFakeApp alloc] initWithName:@"testSetEmulatorSettingsThrowsAfterRepoInit"
484+
URL:self.databaseURL];
485+
FIRDatabase *database = [FIRDatabase databaseForApp:app];
486+
487+
[database reference]; // initialize database repo
488+
489+
// Emulator can't be set after initialization of the database's repo.
490+
XCTAssertThrows([database useEmulatorWithHost:@"a" port:1]);
491+
}
492+
493+
- (void)testEmulatedDatabaseValidatesOnlyNonCustomURLs {
494+
// Set a non-custom databaseURL
495+
NSString *databaseURL = @"https://test.example.com";
496+
id app = [[FIRFakeApp alloc] initWithName:@"testEmulatedDatabaseValidatesNonCustomURLs0"
497+
URL:databaseURL];
498+
FIRDatabase *database = [FIRDatabase databaseForApp:app];
499+
500+
// Reference should be retrievable without an exception being raised
501+
NSString *referenceURLString = [databaseURL stringByAppendingString:@"/path"];
502+
FIRDatabaseReference *reference = [database referenceFromURL:referenceURLString];
503+
XCTAssertNotNil(reference);
504+
505+
app = [[FIRFakeApp alloc] initWithName:@"testEmulatedDatabaseValidatesNonCustomURLs1"
506+
URL:databaseURL];
507+
database = [FIRDatabase databaseForApp:app];
508+
[database useEmulatorWithHost:@"localhost" port:1111];
509+
510+
// Expect production url creates a valid (emulated) reference.
511+
reference = [database referenceFromURL:referenceURLString];
512+
XCTAssertNotNil(reference);
513+
XCTAssert([reference.URL containsString:@"localhost:1111"]);
514+
515+
// Test emulated url
516+
referenceURLString = @"http://localhost:1111/path";
517+
reference = [database referenceFromURL:referenceURLString];
518+
XCTAssertNotNil(reference);
519+
XCTAssert([reference.URL containsString:@"localhost:1111"]);
520+
521+
// Test non-custom url with different host throws exception
522+
referenceURLString = @"https://test.firebaseio.com/path";
523+
XCTAssertThrows([database referenceFromURL:referenceURLString]);
524+
}
525+
472526
- (FIRDatabase *)defaultDatabase {
473527
return [self databaseForURL:self.databaseURL];
474528
}

Firestore/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Unreleased
2+
- [added] Made emulator connection API consistent between Auth, Database, Firestore, and Functions (#5916).
3+
14
# v7.1.0
25
- [changed] Added the original query data to error messages for Queries that
36
cannot be deserizialized.

Firestore/Example/Tests/API/FIRFirestoreTests.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#import <FirebaseFirestore/FIRFirestore.h>
18+
#import <FirebaseFirestore/FIRFirestoreSettings.h>
1819

1920
#import <XCTest/XCTest.h>
2021

@@ -63,4 +64,16 @@ - (void)testDeleteApp {
6364
}];
6465
}
6566

67+
- (void)testSetEmulatorSettingsSetsHost {
68+
// Ensure the app is set appropriately.
69+
FIRApp *app = testutil::AppForUnitTesting();
70+
71+
FIRFirestore *firestore = [FIRFirestore firestoreForApp:app];
72+
73+
[firestore useEmulatorWithHost:@"localhost" port:1000];
74+
75+
NSString *host = firestore.settings.host;
76+
XCTAssertEqualObjects(host, @"localhost:1000");
77+
}
78+
6679
@end

0 commit comments

Comments
 (0)