Files
firebase-ios-sdk/Example/Database/Tests/Integration/FRealtime.m
T
Paul Beusterien ddbfd4cc66 Add travis trailing whitespace check (#116)
* Add travis trailing whitespace check

* Remove trailing white space
2017-06-30 13:47:37 -04:00

606 lines
22 KiB
Objective-C

/*
* 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 "FRealtime.h"
#import "FTupleFirebase.h"
#import "FRepoManager.h"
#import "FUtilities.h"
#import "FParsedUrl.h"
#import "FIRDatabaseConfig_Private.h"
@implementation FRealtime
- (void) testUrlParsing {
FParsedUrl* parsed = [FUtilities parseUrl:@"http://www.example.com:9000"];
XCTAssertTrue([[parsed.path description] isEqualToString:@"/"], @"Got correct path");
XCTAssertTrue([parsed.repoInfo.host isEqualToString:@"www.example.com:9000"], @"Got correct host");
XCTAssertTrue([parsed.repoInfo.internalHost isEqualToString:@"www.example.com:9000"], @"Got correct host");
XCTAssertFalse(parsed.repoInfo.secure, @"Should not be secure, there's a port");
parsed = [FUtilities parseUrl:@"http://www.firebaseio.com/foo/bar"];
XCTAssertTrue([[parsed.path description] isEqualToString:@"/foo/bar"], @"Got correct path");
XCTAssertTrue([parsed.repoInfo.host isEqualToString:@"www.firebaseio.com"], @"Got correct host");
XCTAssertTrue([parsed.repoInfo.internalHost isEqualToString:@"www.firebaseio.com"], @"Got correct host");
XCTAssertTrue(parsed.repoInfo.secure, @"Should be secure, there's no port");
}
- (void) testCachingRedirects {
NSString* host = @"host.example.com";
NSString* host2 = @"host2.example.com";
NSString* internalHost = @"internal.example.com";
NSString* internalHost2 = @"internal2.example.com";
// Set host on first repo info
FRepoInfo* repoInfo = [[FRepoInfo alloc] initWithHost:host isSecure:YES withNamespace:host];
XCTAssertTrue([repoInfo.host isEqualToString:host], @"Got correct host");
XCTAssertTrue([repoInfo.internalHost isEqualToString:host], @"Got correct host");
// Set internal host on first repo info
repoInfo.internalHost = internalHost;
XCTAssertTrue([repoInfo.host isEqualToString:host], @"Got correct host");
XCTAssertTrue([repoInfo.internalHost isEqualToString:internalHost], @"Got correct host");
// Set up a second unrelated repo info to make sure caching is keyspaced properly
FRepoInfo* repoInfo2 = [[FRepoInfo alloc] initWithHost:host2 isSecure:YES withNamespace:host2];
XCTAssertTrue([repoInfo2.host isEqualToString:host2], @"Got correct host");
XCTAssertTrue([repoInfo2.internalHost isEqualToString:host2], @"Got correct host");
repoInfo2.internalHost = internalHost2;
XCTAssertTrue([repoInfo2.internalHost isEqualToString:internalHost2], @"Got correct host");
// Setting host on this repo info should also set the right internal host
FRepoInfo* repoInfoCached = [[FRepoInfo alloc] initWithHost:host isSecure:YES withNamespace:host];
XCTAssertTrue([repoInfoCached.host isEqualToString:host], @"Got correct host");
XCTAssertTrue([repoInfoCached.internalHost isEqualToString:internalHost], @"Got correct host");
[repoInfo clearInternalHostCache];
[repoInfo2 clearInternalHostCache];
[repoInfoCached clearInternalHostCache];
XCTAssertTrue([repoInfo.internalHost isEqualToString:host], @"Got correct host");
XCTAssertTrue([repoInfo2.internalHost isEqualToString:host2], @"Got correct host");
XCTAssertTrue([repoInfoCached.internalHost isEqualToString:host], @"Got correct host");
}
- (void) testOnDisconnectSetWorks {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseConfig *readerCfg = [FIRDatabaseConfig configForName:@"reader"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
FIRDatabaseReference * reader = [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
__block NSNumber* readValue = @0;
__block NSNumber* writeValue = @0;
[[reader child:@"disconnected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
NSNumber *val = [snapshot value];
if (![val isEqual:[NSNull null]]) {
readValue = val;
}
}];
[[writer child:@"disconnected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
id val = [snapshot value];
if (val != [NSNull null]) {
writeValue = val;
}
}];
[writer child:@"hello"];
__block BOOL ready = NO;
[[writer child:@"disconnected"] onDisconnectSetValue:@1 withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref){
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
[writer child:@"s"];
ready = NO;
[[writer child:@"disconnected"] onDisconnectSetValue:@2 withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref){
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return [@2 isEqualToNumber:readValue] && [@2 isEqualToNumber:writeValue];
}];
[FRepoManager interrupt:readerCfg];
// cleanup
[FRepoManager disposeRepos:writerCfg];
[FRepoManager disposeRepos:readerCfg];
}
- (void) testOnDisconnectSetWithPriorityWorks {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseConfig *readerCfg = [FIRDatabaseConfig configForName:@"reader"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
FIRDatabaseReference * reader = [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
__block BOOL sawNewValue = NO;
__block BOOL writerSawNewValue = NO;
[[reader child:@"disconnected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
id val = snapshot.value;
id pri = snapshot.priority;
if (val != [NSNull null] && pri != [NSNull null]) {
sawNewValue = [(NSNumber *) val boolValue] && [pri isEqualToString:@"abcd"];
}
}];
[[writer child:@"disconnected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
id val = [snapshot value];
id pri = snapshot.priority;
if (val != [NSNull null] && pri != [NSNull null]) {
writerSawNewValue = [(NSNumber *) val boolValue] && [pri isEqualToString:@"abcd"];
}
}];
__block BOOL ready = NO;
[[writer child:@"disconnected"] onDisconnectSetValue:@YES andPriority:@"abcd" withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return sawNewValue && writerSawNewValue;
}];
[FRepoManager interrupt:readerCfg];
// cleanup
[FRepoManager disposeRepos:writerCfg];
[FRepoManager disposeRepos:readerCfg];
}
- (void) testOnDisconnectRemoveWorks {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseConfig *readerCfg = [FIRDatabaseConfig configForName:@"reader"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
FIRDatabaseReference * reader = [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
__block BOOL ready = NO;
[[writer child:@"foo"] setValue:@"bar" withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
__block BOOL sawRemove = NO;
__block BOOL writerSawRemove = NO;
[[reader child:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
sawRemove = [[NSNull null] isEqual:snapshot.value];
}];
[[writer child:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
writerSawRemove = [[NSNull null] isEqual:snapshot.value];
}];
ready = NO;
[[writer child:@"foo"] onDisconnectRemoveValueWithCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return sawRemove && writerSawRemove;
}];
[FRepoManager interrupt:readerCfg];
// cleanup
[FRepoManager disposeRepos:writerCfg];
[FRepoManager disposeRepos:readerCfg];
}
- (void) testOnDisconnectUpdateWorks {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseConfig *readerCfg = [FIRDatabaseConfig configForName:@"reader"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
FIRDatabaseReference * reader = [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
[self waitForCompletionOf:[writer child:@"foo"] setValue:@{@"bar": @"a", @"baz": @"b"}];
__block BOOL sawNewValue = NO;
__block BOOL writerSawNewValue = NO;
[[reader child:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
NSDictionary *val = [snapshot value];
if (val) {
sawNewValue = [@{@"bar" : @"a", @"baz" : @"c", @"bat" : @"d"} isEqualToDictionary:val];
}
}];
[[writer child:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
NSDictionary *val = [snapshot value];
if (val) {
writerSawNewValue = [@{@"bar" : @"a", @"baz" : @"c", @"bat" : @"d"} isEqualToDictionary:val];
}
}];
__block BOOL ready = NO;
[[writer child:@"foo"] onDisconnectUpdateChildValues:@{@"baz": @"c", @"bat": @"d"} withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return sawNewValue && writerSawNewValue;
}];
[FRepoManager interrupt:readerCfg];
// cleanup
[FRepoManager disposeRepos:writerCfg];
[FRepoManager disposeRepos:readerCfg];
}
- (void) testOnDisconnectTriggersSingleLocalValueEventForWriter {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
__block int calls = 0;
[writer observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
calls++;
if (calls == 2) {
// second call, verify the data
NSDictionary *val = [snapshot value];
NSDictionary *expected = @{@"foo" : @{@"bar" : @"a", @"bam" : @"c"}};
XCTAssertTrue([val isEqualToDictionary:expected], @"Got all of the updates in one");
} else if (calls > 2) {
XCTFail(@"Extra calls");
}
}];
[self waitUntil:^BOOL{
return calls == 1;
}];
__block BOOL done = NO;
FIRDatabaseReference * child = [writer child:@"foo"];
[child onDisconnectSetValue:@{@"bar": @"a", @"baz": @"b"}];
[child onDisconnectUpdateChildValues:@{@"bam": @"c"}];
[[child child:@"baz"] onDisconnectRemoveValueWithCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
done = YES;
}];
[self waitUntil:^BOOL{
return done;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return calls == 2;
}];
// cleanup
[FRepoManager disposeRepos:writerCfg];
}
- (void) testOnDisconnectTriggersSingleLocalValueEventForReader {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseReference * reader = [FTestHelpers getRandomNode];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] child:reader.key];
__block int calls = 0;
[reader observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
calls++;
if (calls == 2) {
// second call, verify the data
NSDictionary *val = [snapshot value];
NSDictionary *expected = @{@"foo" : @{@"bar" : @"a", @"bam" : @"c"}};
XCTAssertTrue([val isEqualToDictionary:expected], @"Got all of the updates in one");
} else if (calls > 2) {
XCTFail(@"Extra calls");
}
}];
[self waitUntil:^BOOL{
return calls == 1;
}];
__block BOOL done = NO;
FIRDatabaseReference * child = [writer child:@"foo"];
[child onDisconnectSetValue:@{@"bar": @"a", @"baz": @"b"}];
[child onDisconnectUpdateChildValues:@{@"bam": @"c"}];
[[child child:@"baz"] onDisconnectRemoveValueWithCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
done = YES;
}];
[self waitUntil:^BOOL{
return done;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return calls == 2;
}];
// cleanup
[FRepoManager disposeRepos:writerCfg];
}
- (void) testOnDisconnectTriggersSingleLocalValueEventForWriterWithQuery {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
__block int calls = 0;
[[[writer child:@"foo"] queryLimitedToLast:2] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
calls++;
if (calls == 2) {
// second call, verify the data
NSDictionary *val = [snapshot value];
NSDictionary *expected = @{@"bar" : @"a", @"bam" : @"c"};
XCTAssertTrue([val isEqualToDictionary:expected], @"Got all of the updates in one");
} else if (calls > 2) {
XCTFail(@"Extra calls");
}
}];
[self waitUntil:^BOOL{
return calls == 1;
}];
__block BOOL done = NO;
FIRDatabaseReference * child = [writer child:@"foo"];
[child onDisconnectSetValue:@{@"bar": @"a", @"baz": @"b"}];
[child onDisconnectUpdateChildValues:@{@"bam": @"c"}];
[[child child:@"baz"] onDisconnectRemoveValueWithCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
done = YES;
}];
[self waitUntil:^BOOL{
return done;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return calls == 2;
}];
// cleanup
[FRepoManager disposeRepos:writerCfg];
}
- (void) testOnDisconnectTriggersSingleLocalValueEventForReaderWithQuery {
FIRDatabaseReference * reader = [FTestHelpers getRandomNode];
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] child:reader.key];
__block int calls = 0;
[[[reader child:@"foo"] queryLimitedToLast:2] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
calls++;
XCTAssertTrue([snapshot.key isEqualToString:@"foo"], @"Got the right snapshot");
if (calls == 2) {
// second call, verify the data
NSDictionary *val = [snapshot value];
NSDictionary *expected = @{@"bar" : @"a", @"bam" : @"c"};
XCTAssertTrue([val isEqualToDictionary:expected], @"Got all of the updates in one");
} else if (calls > 2) {
XCTFail(@"Extra calls");
}
}];
[self waitUntil:^BOOL{
return calls == 1;
}];
__block BOOL done = NO;
FIRDatabaseReference * child = [writer child:@"foo"];
[child onDisconnectSetValue:@{@"bar": @"a", @"baz": @"b"}];
[child onDisconnectUpdateChildValues:@{@"bam": @"c"}];
[[child child:@"baz"] onDisconnectRemoveValueWithCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
done = YES;
}];
[self waitUntil:^BOOL{
return done;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return calls == 2;
}];
// cleanup
[FRepoManager disposeRepos:writerCfg];
}
- (void) testOnDisconnectDeepMergeTriggersOnlyOneValueEventForReaderWithQuery {
FIRDatabaseReference * reader = [FTestHelpers getRandomNode];
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
__block BOOL done = NO;
NSDictionary* toSet = @{@"a": @1, @"b": @{@"c": @YES, @"d": @"scalar", @"e": @{@"f": @"hooray"}}};
[writer setValue:toSet];
[[writer child:@"a"] onDisconnectSetValue:@2];
[[writer child:@"b/d"] onDisconnectRemoveValueWithCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
done = YES;
}];
WAIT_FOR(done);
__block int count = 2;
[[reader queryLimitedToLast:3] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
count++;
if (count == 1) {
// Loaded the data, kill the writer connection
[FRepoManager interrupt:writerCfg];
} else if (count == 2) {
NSDictionary *expected = @{@"a" : @2, @"b" : @{@"c" : @YES, @"e" : @{@"f" : @"hooray"}}};
XCTAssertTrue([snapshot.value isEqualToDictionary:expected], @"Should see complete new snapshot");
} else {
XCTFail(@"Too many calls");
}
}];
WAIT_FOR(count == 2);
// cleanup
[FRepoManager disposeRepos:writerCfg];
}
- (void) testOnDisconnectCancelWorks {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseConfig *readerCfg = [FIRDatabaseConfig configForName:@"reader"];
FIRDatabaseReference * writer = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
FIRDatabaseReference * reader = [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
__block BOOL ready = NO;
[[writer child:@"foo"] setValue:@{@"bar": @"a", @"baz": @"b"} withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
__block BOOL sawNewValue = NO;
__block BOOL writerSawNewValue = NO;
[[reader child:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
NSDictionary *val = [snapshot value];
if (val) {
sawNewValue = [@{@"bar" : @"a", @"baz" : @"b", @"bat" : @"d"} isEqualToDictionary:val];
}
}];
[[writer child:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
NSDictionary *val = [snapshot value];
if (val) {
writerSawNewValue = [@{@"bar" : @"a", @"baz" : @"b", @"bat" : @"d"} isEqualToDictionary:val];
}
}];
ready = NO;
[[writer child:@"foo"] onDisconnectUpdateChildValues:@{@"baz": @"c", @"bat": @"d"}];
[[writer child:@"foo/baz"] cancelDisconnectOperationsWithCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
ready = YES;
}];
[self waitUntil:^BOOL{
return ready;
}];
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
return sawNewValue && writerSawNewValue;
}];
[FRepoManager interrupt:readerCfg];
// cleanup
[FRepoManager disposeRepos:writerCfg];
[FRepoManager disposeRepos:readerCfg];
}
- (void) testOnDisconnectWithServerValuesWithLocalEvents {
FIRDatabaseConfig *writerCfg = [FIRDatabaseConfig configForName:@"writer"];
FIRDatabaseReference * node = [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
__block FIRDataSnapshot *snap = nil;
[node observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
snap = snapshot;
}];
NSDictionary* data = @{
@"a": @1,
@"b": @{
@".value": [FIRServerValue timestamp],
@".priority": [FIRServerValue timestamp]
}
};
__block BOOL done = NO;
[node onDisconnectSetValue:data andPriority:[FIRServerValue timestamp] withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
done = YES;
}];
[self waitUntil:^BOOL{
return done;
}];
done = NO;
[node onDisconnectUpdateChildValues:@{ @"a": [FIRServerValue timestamp], @"c": [FIRServerValue timestamp] } withCompletionBlock:^(NSError* error, FIRDatabaseReference * ref) {
done = YES;
}];
[self waitUntil:^BOOL{
return done;
}];
done = NO;
[FRepoManager interrupt:writerCfg];
[self waitUntil:^BOOL{
if ([snap value] != [NSNull null]) {
NSDictionary* val = [snap value];
done = (val[@"a"] && val[@"b"] && val[@"c"]);
}
return done;
}];
NSDictionary* value = [snap value];
NSNumber* now = [NSNumber numberWithDouble:round([[NSDate date] timeIntervalSince1970]*1000)];
NSNumber* timestamp = [snap priority];
XCTAssertTrue([[snap priority] isKindOfClass:[NSNumber class]], @"Should get back number");
XCTAssertTrue([now doubleValue] - [timestamp doubleValue] < 2000, @"Number should be no more than 2 seconds ago");
XCTAssertEqualObjects([snap priority], [value objectForKey:@"a"], @"Should get back matching ServerValue.TIMESTAMP");
XCTAssertEqualObjects([snap priority], [value objectForKey:@"b"], @"Should get back matching ServerValue.TIMESTAMP");
XCTAssertEqualObjects([snap priority], [[snap childSnapshotForPath:@"b"] priority], @"Should get back matching ServerValue.TIMESTAMP");
XCTAssertEqualObjects([NSNull null], [[snap childSnapshotForPath:@"d"] value], @"Should get null for cancelled child");
// cleanup
[FRepoManager disposeRepos:writerCfg];
}
@end