diff options
author | 2017-05-15 12:27:07 -0700 | |
---|---|---|
committer | 2017-05-15 12:27:07 -0700 | |
commit | 98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch) | |
tree | 131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Example/Database/Tests/Helpers | |
parent | 32461366c9e204a527ca05e6e9b9404a2454ac51 (diff) |
Initial
Diffstat (limited to 'Example/Database/Tests/Helpers')
27 files changed, 1689 insertions, 0 deletions
diff --git a/Example/Database/Tests/Helpers/FDevice.h b/Example/Database/Tests/Helpers/FDevice.h new file mode 100644 index 0000000..c32aea0 --- /dev/null +++ b/Example/Database/Tests/Helpers/FDevice.h @@ -0,0 +1,36 @@ +/* + * 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 <Foundation/Foundation.h> + +@class FIRDatabaseReference; +@class SenTest; + +@interface FDevice : NSObject +- (id)initOnline; +- (id)initOffline; +- (id)initOnlineWithUrl:(NSString *)firebaseUrl; +- (id)initOfflineWithUrl:(NSString *)firebaseUrl; +- (void)goOffline; +- (void)goOnline; +- (void)restartOnline; +- (void)restartOffline; +- (void)waitForIdleUsingWaiter:(XCTest*)waiter; +- (void)do:(void (^)(FIRDatabaseReference *))action; + +- (void)dispose; + +@end diff --git a/Example/Database/Tests/Helpers/FDevice.m b/Example/Database/Tests/Helpers/FDevice.m new file mode 100644 index 0000000..f9667df --- /dev/null +++ b/Example/Database/Tests/Helpers/FDevice.m @@ -0,0 +1,133 @@ +/* + * 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 <XCTest/XCTest.h> +#import "FDevice.h" +#import "FIRDatabaseReference.h" +#import "FRepoManager.h" +#import "FIRDatabaseReference_Private.h" +#import "FIRDatabaseConfig_Private.h" +#import "SenTest+FWaiter.h" +#import "FTestHelpers.h" + +@interface FDevice() { + FIRDatabaseConfig * config; + NSString *url; + BOOL isOnline; + BOOL disposed; +} +@end + +@implementation FDevice + +- (id)initOnline { + FIRDatabaseReference * ref = [FTestHelpers getRandomNode]; + return [self initOnlineWithUrl:[ref description]]; +} + +- (id)initOffline { + FIRDatabaseReference * ref = [FTestHelpers getRandomNode]; + return [self initOfflineWithUrl:[ref description]]; +} + +- (id)initOnlineWithUrl:(NSString *)firebaseUrl { + return [self initWithUrl:firebaseUrl andOnline:YES]; +} + +- (id)initOfflineWithUrl:(NSString *)firebaseUrl { + return [self initWithUrl:firebaseUrl andOnline:NO]; +} + +static NSUInteger deviceId = 0; + +- (id)initWithUrl:(NSString *)firebaseUrl andOnline:(BOOL)online { + self = [super init]; + if (self) { + config = [FIRDatabaseConfig configForName:[NSString stringWithFormat:@"device-%lu", deviceId++]]; + config.persistenceEnabled = YES; + url = firebaseUrl; + isOnline = online; + } + return self; +} + +- (void) dealloc +{ + if (!self->disposed) { + [NSException raise:NSInternalInconsistencyException format:@"Forgot to dispose device"]; + } +} + +- (void) dispose { + // TODO: clear persistence + [FRepoManager disposeRepos:self->config]; + self->disposed = YES; +} + +- (void)goOffline { + isOnline = NO; + [FRepoManager interrupt:config]; +} + +- (void)goOnline { + isOnline = YES; + [FRepoManager resume:config]; +} + +- (void)restartOnline { + @autoreleasepool { + [FRepoManager disposeRepos:config]; + isOnline = YES; + } +} + +- (void)restartOffline { + @autoreleasepool { + [FRepoManager disposeRepos:config]; + isOnline = NO; + } +} + +// Waits for us to connect and then does an extra round-trip to make sure all initial state restoration is completely done. +- (void)waitForIdleUsingWaiter:(XCTest*)waiter { + [self do:^(FIRDatabaseReference *ref) { + __block BOOL connected = NO; + FIRDatabaseHandle handle = [[ref.root child:@".info/connected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { + connected = [snapshot.value boolValue]; + }]; + [waiter waitUntil:^BOOL { return connected; }]; + [ref.root removeObserverWithHandle:handle]; + + // HACK: Do a deep setPriority (which we expect to fail because there's no data there) to do a no-op roundtrip. + __block BOOL done = NO; + [[ref.root child:@"ENTOHTNUHOE/ONTEHNUHTOE"] setPriority:@"blah" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { + done = YES; + }]; + [waiter waitUntil:^BOOL { return done; }]; + }]; +} + +- (void)do:(void (^)(FIRDatabaseReference *))action { + @autoreleasepool { + FIRDatabaseReference *ref = [[[[FIRDatabaseReference alloc] initWithConfig:self->config] database] referenceFromURL:self->url]; + if (!isOnline) { + [FRepoManager interrupt:config]; + } + action(ref); + } +} + +@end diff --git a/Example/Database/Tests/Helpers/FEventTester.h b/Example/Database/Tests/Helpers/FEventTester.h new file mode 100644 index 0000000..b3503b9 --- /dev/null +++ b/Example/Database/Tests/Helpers/FEventTester.h @@ -0,0 +1,37 @@ +/* + * 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 <Foundation/Foundation.h> +#import <XCTest/XCTest.h> + +@interface FEventTester : XCTestCase + +- (id)initFrom:(XCTestCase *)elsewhere; +- (void) addLookingFor:(NSArray *)l; +- (void) wait; +- (void) waitForInitialization; +- (void) unregister; + +@property (nonatomic, strong) NSMutableArray* lookingFor; +@property (readwrite) int callbacksCalled; +@property (nonatomic, strong) NSMutableDictionary* seenFirebaseLocations; +//@property (nonatomic, strong) NSMutableDictionary* initializationEvents; +@property (nonatomic, strong) XCTestCase* from; +@property (nonatomic, strong) NSMutableArray* errors; +@property (nonatomic, strong) NSMutableArray* actualPathsAndEvents; +@property (nonatomic) int initializationEvents; + +@end diff --git a/Example/Database/Tests/Helpers/FEventTester.m b/Example/Database/Tests/Helpers/FEventTester.m new file mode 100644 index 0000000..fa7c081 --- /dev/null +++ b/Example/Database/Tests/Helpers/FEventTester.m @@ -0,0 +1,172 @@ +/* + * 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 "FEventTester.h" +#import "FIRDatabaseReference.h" +#import "FTupleBoolBlock.h" +#import "FTupleEventTypeString.h" +#import "FTestHelpers.h" +#import "SenTest+FWaiter.h" + +@implementation FEventTester + +@synthesize lookingFor; +@synthesize callbacksCalled; +@synthesize from; +@synthesize errors; +@synthesize seenFirebaseLocations; +@synthesize initializationEvents; +@synthesize actualPathsAndEvents; + +- (id)initFrom:(XCTestCase *)elsewhere +{ + self = [super init]; + if (self) { + self.seenFirebaseLocations = [[NSMutableDictionary alloc] init]; + self.initializationEvents = 0; + self.lookingFor = [[NSMutableArray alloc] init]; + self.actualPathsAndEvents = [[NSMutableArray alloc] init]; + self.from = elsewhere; + self.callbacksCalled = 0; + } + return self; +} + +- (void) addLookingFor:(NSArray *)l { + + // expect them in the order they're given to us + [self.lookingFor addObjectsFromArray:l]; + + + // see notes on ordering of listens in init.spec.js + NSArray* toListen = [l sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { + FTupleEventTypeString* a = obj1; + FTupleEventTypeString* b = obj2; + NSUInteger lenA = [a.firebase description].length; + NSUInteger lenB = [b.firebase description].length; + if (lenA < lenB) { + return NSOrderedAscending; + } else if (lenA == lenB) { + return NSOrderedSame; + } else { + return NSOrderedDescending; + } + }]; + + for(FTupleEventTypeString* fevts in toListen) { + if(! [self.seenFirebaseLocations objectForKey:[fevts.firebase description]]) { + fevts.vvcallback = [self listenOnPath:fevts.firebase]; + fevts.initialized = NO; + [self.seenFirebaseLocations setObject:fevts forKey:[fevts.firebase description]]; + } + } +} + +- (void) unregister { + for(FTupleEventTypeString* fevts in self.lookingFor) { + if (fevts.vvcallback) { + fevts.vvcallback(); + } + } + [self.lookingFor removeAllObjects]; +} + +- (fbt_void_void) listenOnPath:(FIRDatabaseReference *)path { + FIRDatabaseHandle removedHandle = [path observeEventType:FIRDataEventTypeChildRemoved withBlock:[self makeEventCallback:FIRDataEventTypeChildRemoved]]; + FIRDatabaseHandle addedHandle = [path observeEventType:FIRDataEventTypeChildAdded withBlock:[self makeEventCallback:FIRDataEventTypeChildAdded]]; + FIRDatabaseHandle movedHandle = [path observeEventType:FIRDataEventTypeChildMoved withBlock:[self makeEventCallback:FIRDataEventTypeChildMoved]]; + FIRDatabaseHandle changedHandle = [path observeEventType:FIRDataEventTypeChildChanged withBlock:[self makeEventCallback:FIRDataEventTypeChildChanged]]; + FIRDatabaseHandle valueHandle = [path observeEventType:FIRDataEventTypeValue withBlock:[self makeEventCallback:FIRDataEventTypeValue]]; + + fbt_void_void cb = ^() { + [path removeObserverWithHandle:removedHandle]; + [path removeObserverWithHandle:addedHandle]; + [path removeObserverWithHandle:movedHandle]; + [path removeObserverWithHandle:changedHandle]; + [path removeObserverWithHandle:valueHandle]; + }; + return [cb copy]; +} + +- (void) wait { + [self waitUntil:^BOOL{ + return self.actualPathsAndEvents.count >= self.lookingFor.count; + } timeout:kFirebaseTestTimeout]; + + for (int i = 0; i < self.lookingFor.count; ++i) { + FTupleEventTypeString* target = [self.lookingFor objectAtIndex:i]; + FTupleEventTypeString* recvd = [self.actualPathsAndEvents objectAtIndex:i]; + XCTAssertTrue([target isEqualTo:recvd], @"Expected %@ to match %@", target, recvd); + } + + if (self.actualPathsAndEvents.count > self.lookingFor.count) { + NSLog(@"Too many events: %@", self.actualPathsAndEvents); + XCTFail(@"Received too many events"); + } +} + +- (void) waitForInitialization { + [self waitUntil:^BOOL{ + for (FTupleEventTypeString* evt in [self.seenFirebaseLocations allValues]) { + if (!evt.initialized) { + return NO; + } + } + + // splice out all of the initialization events + NSRange theRange; + theRange.location = 0; + theRange.length = self.initializationEvents; + [actualPathsAndEvents removeObjectsInRange:theRange]; + + return YES; + } timeout:kFirebaseTestTimeout]; +} + +- (fbt_void_datasnapshot) makeEventCallback:(FIRDataEventType)type { + + fbt_void_datasnapshot cb = ^(FIRDataSnapshot * snap) { + + FIRDatabaseReference * ref = snap.ref; + NSString* name = nil; + if (type != FIRDataEventTypeValue) { + ref = ref.parent; + name = snap.key; + } + + FTupleEventTypeString* evt = [[FTupleEventTypeString alloc] initWithFirebase:ref withEvent:type withString:name]; + [actualPathsAndEvents addObject:evt]; + + NSLog(@"Adding event: %@ (%@)", evt, [snap value]); + + FTupleEventTypeString* targetEvt = [self.seenFirebaseLocations objectForKey:[ref description]]; + if (targetEvt && !targetEvt.initialized) { + self.initializationEvents++; + if (type == FIRDataEventTypeValue) { + targetEvt.initialized = YES; + } + } + }; + return [cb copy]; +} + + +- (void) failWithException:(NSException *) anException { + //TODO: FIX + @throw anException; +} + +@end diff --git a/Example/Database/Tests/Helpers/FIRFakeApp.h b/Example/Database/Tests/Helpers/FIRFakeApp.h new file mode 100644 index 0000000..afe976a --- /dev/null +++ b/Example/Database/Tests/Helpers/FIRFakeApp.h @@ -0,0 +1,27 @@ +/* + * 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 <Foundation/Foundation.h> + +@class FIRFakeOptions; + +@interface FIRFakeApp : NSObject + +- (instancetype) initWithName:(NSString *)name URL:(NSString *)url; + +@property(nonatomic, readonly) FIRFakeOptions *options; +@property(nonatomic, copy, readonly) NSString *name; +@end diff --git a/Example/Database/Tests/Helpers/FIRFakeApp.m b/Example/Database/Tests/Helpers/FIRFakeApp.m new file mode 100644 index 0000000..b7abe81 --- /dev/null +++ b/Example/Database/Tests/Helpers/FIRFakeApp.m @@ -0,0 +1,48 @@ +/* + * 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 "FIRFakeApp.h" + +@interface FIRFakeOptions: NSObject +@property(nonatomic, readonly, copy) NSString *databaseURL; +- (instancetype) initWithURL:(NSString *)url; +@end + +@implementation FIRFakeOptions +- (instancetype) initWithURL:(NSString *)url { + self = [super init]; + if (self) { + self->_databaseURL = url; + } + return self; +} +@end + +@implementation FIRFakeApp + +- (instancetype) initWithName:(NSString *)name URL:(NSString *)url { + self = [super init]; + if (self) { + self->_name = name; + self->_options = [[FIRFakeOptions alloc] initWithURL:url]; + } + return self; +} + +- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(void (^)(NSString *_Nullable token, NSError *_Nullable error))callback { + callback(nil, nil); +} +@end diff --git a/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.h b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.h new file mode 100644 index 0000000..e2a5751 --- /dev/null +++ b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.h @@ -0,0 +1,28 @@ +/* + * 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 <Foundation/Foundation.h> +#import "FAuthTokenProvider.h" + +@interface FIRTestAuthTokenProvider : NSObject <FAuthTokenProvider> + +@property (nonatomic, strong) NSString *token; +@property (nonatomic, strong) NSString *nextToken; + +- (instancetype) initWithToken:(NSString *)token; +- (instancetype) init NS_UNAVAILABLE; + +@end diff --git a/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.m b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.m new file mode 100644 index 0000000..4719295 --- /dev/null +++ b/Example/Database/Tests/Helpers/FIRTestAuthTokenProvider.m @@ -0,0 +1,61 @@ +/* + * 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 "FIRTestAuthTokenProvider.h" +#import "FIRDatabaseQuery_Private.h" + +@interface FIRTestAuthTokenProvider () + +@property (nonatomic, strong) NSMutableArray *listeners; + +@end + +@implementation FIRTestAuthTokenProvider + +- (instancetype) initWithToken:(NSString *)token { + self = [super init]; + if (self != nil) { + self.listeners = [NSMutableArray array]; + self.token = token; + } + return self; +} + +- (void) setToken:(NSString *)token { + self->_token = token; + dispatch_async([FIRDatabaseQuery sharedQueue], ^{ + [self.listeners enumerateObjectsUsingBlock:^(fbt_void_nsstring _Nonnull listener, NSUInteger idx, BOOL * _Nonnull stop) { + listener(token); + }]; + }); + +} + +- (void) fetchTokenForcingRefresh:(BOOL)forceRefresh withCallback:(fbt_void_nsstring_nserror)callback { + if (forceRefresh) { + self.token = self.nextToken; + } + // Simulate delay + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)), [FIRDatabaseQuery sharedQueue], ^{ + callback(self.token, nil); + }); +} + +- (void) listenForTokenChanges:(fbt_void_nsstring)listener { + [self.listeners addObject:[listener copy]]; +} + +@end diff --git a/Example/Database/Tests/Helpers/FMockStorageEngine.h b/Example/Database/Tests/Helpers/FMockStorageEngine.h new file mode 100644 index 0000000..98a7d84 --- /dev/null +++ b/Example/Database/Tests/Helpers/FMockStorageEngine.h @@ -0,0 +1,23 @@ +/* + * 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 <Foundation/Foundation.h> + +#import "FStorageEngine.h" + +@interface FMockStorageEngine : NSObject<FStorageEngine> + +@end diff --git a/Example/Database/Tests/Helpers/FMockStorageEngine.m b/Example/Database/Tests/Helpers/FMockStorageEngine.m new file mode 100644 index 0000000..98cb596 --- /dev/null +++ b/Example/Database/Tests/Helpers/FMockStorageEngine.m @@ -0,0 +1,168 @@ +/* + * 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 "FMockStorageEngine.h" + +#import "FWriteRecord.h" +#import "FCompoundWrite.h" +#import "FNode.h" +#import "FEmptyNode.h" +#import "FTrackedQuery.h" +#import "FPruneForest.h" +#import "FCompoundWrite.h" + +@interface FMockStorageEngine () + +@property (nonatomic) BOOL closed; +@property (nonatomic, strong) NSMutableDictionary *userWritesDict; +@property (nonatomic, strong) FCompoundWrite *serverCache; +@property (nonatomic, strong) NSMutableDictionary *trackedQueries; +@property (nonatomic, strong) NSMutableDictionary *trackedQueryKeys; + +@end + +@implementation FMockStorageEngine + +- (id)init { + self = [super init]; + if (self != nil) { + self->_userWritesDict = [NSMutableDictionary dictionary]; + self->_serverCache = [FCompoundWrite emptyWrite]; + self->_trackedQueries = [NSMutableDictionary dictionary]; + self->_trackedQueryKeys = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)close { + self.closed = YES; +} + +- (void)saveUserOverwrite:(id<FNode>)node atPath:(FPath *)path writeId:(NSUInteger)writeId { + FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path overwrite:node writeId:writeId visible:YES]; + self.userWritesDict[@(writeId)] = writeRecord; +} + +- (void)saveUserMerge:(FCompoundWrite *)merge atPath:(FPath *)path writeId:(NSUInteger)writeId { + FWriteRecord *writeRecord = [[FWriteRecord alloc] initWithPath:path merge:merge writeId:writeId]; + self.userWritesDict[@(writeId)] = writeRecord; +} + +- (void)removeUserWrite:(NSUInteger)writeId { + [self.userWritesDict removeObjectForKey:@(writeId)]; +} + +- (void)removeAllUserWrites { + [self.userWritesDict removeAllObjects]; +} + +- (NSArray *)userWrites { + return [[self.userWritesDict allValues] sortedArrayUsingComparator:^NSComparisonResult(FWriteRecord *obj1, FWriteRecord *obj2) { + if (obj1.writeId < obj2.writeId) { + return NSOrderedAscending; + } else if (obj1.writeId > obj2.writeId) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; +} + +- (id<FNode>)serverCacheAtPath:(FPath *)path { + return [[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]]; +} + +- (id<FNode>)serverCacheForKeys:(NSSet *)keys atPath:(FPath *)path { + __block id<FNode> children = [FEmptyNode emptyNode]; + id<FNode> fullNode = [[self.serverCache childCompoundWriteAtPath:path] applyToNode:[FEmptyNode emptyNode]]; + [keys enumerateObjectsUsingBlock:^(NSString *key, BOOL *stop) { + children = [children updateImmediateChild:key withNewChild:[fullNode getImmediateChild:key]]; + }]; + return children; +} + +- (void)updateServerCache:(id<FNode>)node atPath:(FPath *)path merge:(BOOL)merge { + if (merge) { + [node enumerateChildrenUsingBlock:^(NSString *key, id<FNode> childNode, BOOL *stop) { + self.serverCache = [self.serverCache addWrite:childNode atPath:[path childFromString:key]]; + }]; + } else { + self.serverCache = [self.serverCache addWrite:node atPath:path]; + } +} + +- (void)updateServerCacheWithMerge:(FCompoundWrite *)merge atPath:(FPath *)path { + self.serverCache = [self.serverCache addCompoundWrite:merge atPath:path]; +} + +- (NSUInteger)serverCacheEstimatedSizeInBytes { + id data = [[self.serverCache applyToNode:[FEmptyNode emptyNode]] valForExport:YES]; + return [NSJSONSerialization dataWithJSONObject:data options:0 error:nil].length; +} + +- (void)pruneCache:(FPruneForest *)pruneForest atPath:(FPath *)prunePath { + [self.serverCache enumerateWrites:^(FPath *absolutePath, id<FNode> node, BOOL *stop) { + NSAssert([prunePath isEqual:absolutePath] || ![absolutePath contains:prunePath], @"Pruning at %@ but we found data higher up!", prunePath); + if ([prunePath contains:absolutePath]) { + FPath *relativePath = [FPath relativePathFrom:prunePath to:absolutePath]; + if ([pruneForest shouldPruneUnkeptDescendantsAtPath:relativePath]) { + __block FCompoundWrite *newCache = [FCompoundWrite emptyWrite]; + [[pruneForest childAtPath:relativePath] enumarateKeptNodesUsingBlock:^(FPath *keepPath) { + newCache = [newCache addWrite:[node getChild:keepPath] atPath:keepPath]; + }]; + self.serverCache = [[self.serverCache removeWriteAtPath:absolutePath] addCompoundWrite:newCache atPath:absolutePath]; + } else { + // NOTE: This is technically a valid scenario (e.g. you ask to prune at / but only want to prune + // 'foo' and 'bar' and ignore everything else). But currently our pruning will explicitly + // prune or keep everything we know about, so if we hit this it means our tracked queries and + // the server cache are out of sync. + NSAssert([pruneForest shouldKeepPath:relativePath], @"We have data at %@ that is neither pruned nor kept.", relativePath); + } + } + }]; +} + +- (NSArray *)loadTrackedQueries { + return self.trackedQueries.allValues; +} + +- (void)removeTrackedQuery:(NSUInteger)queryId { + [self.trackedQueries removeObjectForKey:@(queryId)]; + [self.trackedQueryKeys removeObjectForKey:@(queryId)]; +} + +- (void)saveTrackedQuery:(FTrackedQuery *)query { + self.trackedQueries[@(query.queryId)] = query; +} + +- (void)setTrackedQueryKeys:(NSSet *)keys forQueryId:(NSUInteger)queryId { + self.trackedQueryKeys[@(queryId)] = keys; +} + +- (void)updateTrackedQueryKeysWithAddedKeys:(NSSet *)added removedKeys:(NSSet *)removed forQueryId:(NSUInteger)queryId { + NSSet *oldKeys = [self trackedQueryKeysForQuery:queryId]; + NSMutableSet *newKeys = [NSMutableSet setWithSet:oldKeys]; + [newKeys minusSet:removed]; + [newKeys unionSet:added]; + self.trackedQueryKeys[@(queryId)] = newKeys; +} + +- (NSSet *)trackedQueryKeysForQuery:(NSUInteger)queryId { + NSSet *keys = self.trackedQueryKeys[@(queryId)]; + return keys != nil ? keys : [NSSet set]; +} + +@end diff --git a/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.h b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.h new file mode 100644 index 0000000..d6d9fd3 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.h @@ -0,0 +1,23 @@ +/* + * 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 <Foundation/Foundation.h> + +@interface FTestAuthTokenGenerator : NSObject + ++ (NSString *) tokenWithSecret:(NSString *)secret authData:(NSDictionary *)data andOptions:(NSDictionary *)options; + +@end diff --git a/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.m b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.m new file mode 100644 index 0000000..bd98e82 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestAuthTokenGenerator.m @@ -0,0 +1,90 @@ +/* + * 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 <CommonCrypto/CommonHMAC.h> +#import "FTestAuthTokenGenerator.h" +#import "Base64.h" + +@implementation FTestAuthTokenGenerator + ++ (NSString *) jsonStringForData:(id)data { + NSData* jsonData = [NSJSONSerialization dataWithJSONObject:data + options:kNilOptions error:nil]; + + return [[NSString alloc] initWithData:jsonData + encoding:NSUTF8StringEncoding]; +} + ++ (NSNumber *) tokenVersion { + return @0; +} + ++ (NSMutableDictionary *) createOptionsClaims:(NSDictionary *)options { + NSMutableDictionary* claims = [[NSMutableDictionary alloc] init]; + if (options) { + NSDictionary* map = @{ + @"expires": @"exp", + @"notBefore": @"nbf", + @"admin": @"admin", + @"debug": @"debug", + @"simulate": @"simulate" + }; + + for (NSString* claim in map) { + if (options[claim] != nil) { + NSString* claimName = [map objectForKey:claim]; + id val = [options objectForKey:claim]; + [claims setObject:val forKey:claimName]; + } + } + } + return claims; +} + ++ (NSString *) webSafeBase64:(NSString *)encoded { + return [[[encoded stringByReplacingOccurrencesOfString:@"=" withString:@""] stringByReplacingOccurrencesOfString:@"+" withString:@"-"] stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; +} + ++ (NSString *) base64EncodeString:(NSString *)target { + return [self webSafeBase64:[target base64EncodedString]]; +} + ++ (NSString *) tokenWithClaims:(NSDictionary *)claims andSecret:(NSString *)secret { + NSDictionary* headerData = @{@"typ": @"JWT", @"alg": @"HS256"}; + NSString* encodedHeader = [self base64EncodeString:[self jsonStringForData:headerData]]; + NSString* encodedClaims = [self base64EncodeString:[self jsonStringForData:claims]]; + + NSString* secureBits = [NSString stringWithFormat:@"%@.%@", encodedHeader, encodedClaims]; + + const char *cKey = [secret cStringUsingEncoding:NSUTF8StringEncoding]; + const char *cData = [secureBits cStringUsingEncoding:NSUTF8StringEncoding]; + unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH]; + CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC); + NSData* hmac = [NSData dataWithBytesNoCopy:cHMAC length:CC_SHA256_DIGEST_LENGTH freeWhenDone:NO]; + NSString* encodedHmac = [self webSafeBase64:[hmac base64EncodedString]]; + return [NSString stringWithFormat:@"%@.%@.%@", encodedHeader, encodedClaims, encodedHmac]; +} + ++ (NSString *) tokenWithSecret:(NSString *)secret authData:(NSDictionary *)data andOptions:(NSDictionary *)options { + NSMutableDictionary* claims = [self createOptionsClaims:options]; + [claims setObject:[self tokenVersion] forKey:@"v"]; + NSNumber* now = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]]; + [claims setObject:now forKey:@"iat"]; + [claims setObject:data forKey:@"d"]; + return [self tokenWithClaims:claims andSecret:secret]; +} + +@end diff --git a/Example/Database/Tests/Helpers/FTestBase.h b/Example/Database/Tests/Helpers/FTestBase.h new file mode 100644 index 0000000..8137b94 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestBase.h @@ -0,0 +1,38 @@ +/* + * 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 <XCTest/XCTest.h> +#import "FTestHelpers.h" +#import "SenTest+FWaiter.h" + +@interface FTestBase : XCTestCase { + BOOL runPerfTests; +} + +- (void)snapWaiter:(FIRDatabaseReference *)path withBlock:(fbt_void_datasnapshot)fn; +- (void)waitUntilConnected:(FIRDatabaseReference *)ref; +- (void)waitForQueue:(FIRDatabaseReference *)ref; +- (void)waitForEvents:(FIRDatabaseReference *)ref; +- (void)waitForRoundTrip:(FIRDatabaseReference *)ref; +- (void)waitForValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected; +- (void)waitForExportValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected; +- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value; +- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value andPriority:(id)priority; +- (void)waitForCompletionOf:(FIRDatabaseReference *)ref updateChildValues:(NSDictionary *)values; + +@property(nonatomic, readonly) NSString *databaseURL; + +@end diff --git a/Example/Database/Tests/Helpers/FTestBase.m b/Example/Database/Tests/Helpers/FTestBase.m new file mode 100644 index 0000000..f55c73b --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestBase.m @@ -0,0 +1,170 @@ +/* + * 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 "FIRApp.h" +#import "FIROptions.h" +#import "FTestBase.h" +#import "FTestAuthTokenGenerator.h" +#import "FIRDatabaseQuery_Private.h" +#import "FIRTestAuthTokenProvider.h" + +@implementation FTestBase + ++ (void)setUp +{ + static dispatch_once_t once; + dispatch_once(&once, ^ { + [FIRApp configure]; + }); +} + +- (void)setUp +{ + [super setUp]; + + [FIRDatabase setLoggingEnabled:YES]; + _databaseURL = [[FIRApp defaultApp] options].databaseURL; + + // Disabled normally since they slow down the tests and don't actually assert anything (they just NSLog timings). + runPerfTests = NO; +} + +- (void)snapWaiter:(FIRDatabaseReference *)path withBlock:(fbt_void_datasnapshot)fn { + __block BOOL done = NO; + + [path observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snap) { + fn(snap); + done = YES; + }]; + + NSTimeInterval timeTaken = [self waitUntil:^BOOL{ + return done; + } timeout:kFirebaseTestWaitUntilTimeout]; + + NSLog(@"snapWaiter:withBlock: timeTaken:%f", timeTaken); + + XCTAssertTrue(done, @"Properly finished."); +} + +- (void) waitUntilConnected:(FIRDatabaseReference *)ref { + __block BOOL connected = NO; + FIRDatabaseHandle handle = [[ref.root child:@".info/connected"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { + connected = [snapshot.value boolValue]; + }]; + WAIT_FOR(connected); + [ref.root removeObserverWithHandle:handle]; +} + +- (void) waitForRoundTrip:(FIRDatabaseReference *)ref { + // HACK: Do a deep setPriority (which we expect to fail because there's no data there) to do a no-op roundtrip. + __block BOOL done = NO; + [[ref.root child:@"ENTOHTNUHOE/ONTEHNUHTOE"] setPriority:@"blah" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { + done = YES; + }]; + WAIT_FOR(done); +} + +- (void) waitForQueue:(FIRDatabaseReference *)ref { + dispatch_sync([FIRDatabaseQuery sharedQueue], ^{}); +} + +- (void) waitForEvents:(FIRDatabaseReference *)ref { + [self waitForQueue:ref]; + __block BOOL done = NO; + dispatch_async(dispatch_get_main_queue(), ^{ + done = YES; + }); + WAIT_FOR(done); +} + +- (void)waitForValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected { + __block id value; + FIRDatabaseHandle handle = [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { + value = snapshot.value; + }]; + + @try { + [self waitUntil:^BOOL { + return [value isEqual:expected]; + }]; + } @catch (NSException *exception) { + @throw [NSException exceptionWithName:@"DidNotGetValue" reason:@"Did not get expected value" + userInfo:@{ @"expected": (!expected ? @"nil" : expected), + @"actual": (!value ? @"nil" : value) }]; + } @finally { + [ref removeObserverWithHandle:handle]; + } +} + +- (void)waitForExportValueOf:(FIRDatabaseQuery *)ref toBe:(id)expected { + __block id value; + FIRDatabaseHandle handle = [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { + value = snapshot.valueInExportFormat; + }]; + + @try { + [self waitUntil:^BOOL { + return [value isEqual:expected]; + }]; + } @catch (NSException *exception) { + if ([exception.name isEqualToString:@"Timed out"]) { + @throw [NSException exceptionWithName:@"DidNotGetValue" reason:@"Did not get expected value" + userInfo:@{ @"expected": (!expected ? @"nil" : expected), + @"actual": (!value ? @"nil" : value) }]; } else { + @throw exception; + } + } @finally { + [ref removeObserverWithHandle:handle]; + } +} + +- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value { + [self waitForCompletionOf:ref setValue:value andPriority:nil]; +} + +- (void)waitForCompletionOf:(FIRDatabaseReference *)ref setValue:(id)value andPriority:(id)priority { + __block BOOL done = NO; + [ref setValue:value andPriority:priority withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { + done = YES; + }]; + + @try { + WAIT_FOR(done); + } @catch (NSException *exception) { + @throw [NSException exceptionWithName:@"DidNotSetValue" reason:@"Did not complete setting value" + userInfo:@{ @"ref": [ref description], + @"done": done ? @"true" : @"false", + @"value": (!value ? @"nil" : value), + @"priority": (!priority ? @"nil" : priority) }]; + } +} + +- (void)waitForCompletionOf:(FIRDatabaseReference *)ref updateChildValues:(NSDictionary *)values { + __block BOOL done = NO; + [ref updateChildValues:values withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { + done = YES; + }]; + + @try { + WAIT_FOR(done); + } @catch (NSException *exception) { + @throw [NSException exceptionWithName:@"DidNotUpdateChildValues" reason:@"Could not finish updating child values" + userInfo:@{ @"ref": [ref description], + @"values": (!values ? @"nil" : values)}]; + } +} + +@end diff --git a/Example/Database/Tests/Helpers/FTestCachePolicy.h b/Example/Database/Tests/Helpers/FTestCachePolicy.h new file mode 100644 index 0000000..688c21d --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestCachePolicy.h @@ -0,0 +1,27 @@ +/* + * 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 <Foundation/Foundation.h> + +#import "FCachePolicy.h" + +@interface FTestCachePolicy : NSObject<FCachePolicy> + +- (id)initWithPercent:(float)percent maxQueries:(NSUInteger)maxQueries; + +- (void)pruneOnNextCheck; + +@end diff --git a/Example/Database/Tests/Helpers/FTestCachePolicy.m b/Example/Database/Tests/Helpers/FTestCachePolicy.m new file mode 100644 index 0000000..aacd010 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestCachePolicy.m @@ -0,0 +1,65 @@ +/* + * 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 "FTestCachePolicy.h" + +@interface FTestCachePolicy () + + +@property (nonatomic) float percentOfQueries; +@property (nonatomic) NSUInteger maxTrackedQueries; +@property (nonatomic) BOOL pruneNext; + +@end + +@implementation FTestCachePolicy + +- (id)initWithPercent:(float)percent maxQueries:(NSUInteger)maxQueries { + self = [super init]; + if (self != nil) { + self->_maxTrackedQueries = maxQueries; + self->_percentOfQueries = percent; + self->_pruneNext = NO; + } + return self; +} + +- (void)pruneOnNextCheck { + self.pruneNext = YES; +} + +- (BOOL)shouldPruneCacheWithSize:(NSUInteger)cacheSize numberOfTrackedQueries:(NSUInteger)numTrackedQueries { + if (self.pruneNext) { + self.pruneNext = NO; + return YES; + } else { + return NO; + } +} + +- (BOOL)shouldCheckCacheSize:(NSUInteger)serverUpdatesSinceLastCheck { + return YES; +} + +- (float)percentOfQueriesToPruneAtOnce { + return self.percentOfQueries; +} + +- (NSUInteger)maxNumberOfQueriesToKeep { + return self.maxTrackedQueries; +} + +@end diff --git a/Example/Database/Tests/Helpers/FTestClock.h b/Example/Database/Tests/Helpers/FTestClock.h new file mode 100644 index 0000000..5520c6a --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestClock.h @@ -0,0 +1,28 @@ +/* + * 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 <Foundation/Foundation.h> + +#import "FClock.h" + +@interface FTestClock : NSObject<FClock> + +@property (nonatomic, readonly) NSTimeInterval currentTime; + +- (id)init; +- (void)tick; + +@end diff --git a/Example/Database/Tests/Helpers/FTestClock.m b/Example/Database/Tests/Helpers/FTestClock.m new file mode 100644 index 0000000..43599ac --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestClock.m @@ -0,0 +1,33 @@ +/* + * 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 "FTestClock.h" + +@implementation FTestClock + +- (id)init { + self = [super init]; + if (self != nil) { + self->_currentTime = 0.001; + } + return self; +} + +- (void)tick { + self->_currentTime = self->_currentTime + 0.001; +} + +@end diff --git a/Example/Database/Tests/Helpers/FTestContants.h b/Example/Database/Tests/Helpers/FTestContants.h new file mode 100644 index 0000000..bc8dd8d --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestContants.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#ifndef Firebase_FTestContants_h +#define Firebase_FTestContants_h + +#define kFirebaseTestTimeout 7 +#define kFirebaseTestWaitUntilTimeout 5 + +#endif diff --git a/Example/Database/Tests/Helpers/FTestExpectations.h b/Example/Database/Tests/Helpers/FTestExpectations.h new file mode 100644 index 0000000..8a797c8 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestExpectations.h @@ -0,0 +1,32 @@ +/* + * 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 <Foundation/Foundation.h> +#import <XCTest/XCTest.h> +#import "FIRDatabaseQuery.h" + +@interface FTestExpectations : XCTestCase { + NSMutableArray* expectations; + XCTestCase* from; +} + +- (id) initFrom:(XCTestCase *)other; +- (void)addQuery:(FIRDatabaseQuery *)query withExpectation:(id)expectation; +- (void) validate; + +@property (readonly) BOOL isReady; + +@end diff --git a/Example/Database/Tests/Helpers/FTestExpectations.m b/Example/Database/Tests/Helpers/FTestExpectations.m new file mode 100644 index 0000000..d0f84d7 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestExpectations.m @@ -0,0 +1,88 @@ +/* + * 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 "FTestExpectations.h" +#import "FIRDataSnapshot.h" + +@interface FExpectation : NSObject + +@property (strong, nonatomic) FIRDatabaseQuery * query; +@property (strong, nonatomic) id expectation; +@property (strong, nonatomic) FIRDataSnapshot * snap; + +@end + +@implementation FExpectation + +@synthesize query; +@synthesize expectation; +@synthesize snap; + +@end + +@implementation FTestExpectations + +- (id) initFrom:(XCTestCase *)other { + self = [super init]; + if (self) { + expectations = [[NSMutableArray alloc] init]; + from = other; + } + return self; +} + +- (void)addQuery:(FIRDatabaseQuery *)query withExpectation:(id)expectation { + FExpectation* exp = [[FExpectation alloc] init]; + exp.query = query; + exp.expectation = expectation; + [query observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { + exp.snap = snapshot; + }]; + [expectations addObject:exp]; +} + +- (BOOL) isReady { + for (FExpectation* exp in expectations) { + if (!exp.snap) { + return NO; + } + // Note that a failure here will end up triggering the timeout + FIRDataSnapshot * snap = exp.snap; + NSDictionary* result = snap.value; + NSDictionary* expected = exp.expectation; + if ([result isEqual:[NSNull null]] || ![result isEqualToDictionary:expected]) { + return NO; + } + } + return YES; +} + +- (void) validate { + for (FExpectation* exp in expectations) { + FIRDataSnapshot * snap = exp.snap; + NSDictionary* result = [snap value]; + NSDictionary* expected = exp.expectation; + XCTAssertTrue([result isEqualToDictionary:expected], @"Expectation mismatch: %@ should be %@", result, expected); + } +} + +- (void) failWithException:(NSException *) anException { + @throw anException; + // TODO: fix + //[from failWithException:anException]; +} + +@end diff --git a/Example/Database/Tests/Helpers/FTestHelpers.h b/Example/Database/Tests/Helpers/FTestHelpers.h new file mode 100644 index 0000000..679be7e --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestHelpers.h @@ -0,0 +1,38 @@ +/* + * 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 <Foundation/Foundation.h> +#import <XCTest/XCTest.h> +#import "FTupleFirebase.h" +#import "FRepoManager.h" +#import "FIRDatabaseReference_Private.h" +#import "FTestContants.h" +#import "FSnapshotUtilities.h" + +#define WAIT_FOR(x) [self waitUntil:^{ return (BOOL)(x); }]; + +#define NODE(__node) [FSnapshotUtilities nodeFrom:(__node)] +#define PATH(__path) [FPath pathWithString:(__path)] + +@interface FTestHelpers : XCTestCase ++ (FIRDatabaseReference *) getRandomNode; ++ (FIRDatabaseReference *) getRandomNodeWithoutPersistence; ++ (FTupleFirebase *) getRandomNodePair; ++ (FTupleFirebase *) getRandomNodePairWithoutPersistence; ++ (FTupleFirebase *) getRandomNodeTriple; ++ (id<FNode>)leafNodeOfSize:(NSUInteger)size; + +@end diff --git a/Example/Database/Tests/Helpers/FTestHelpers.m b/Example/Database/Tests/Helpers/FTestHelpers.m new file mode 100644 index 0000000..8ffdc7d --- /dev/null +++ b/Example/Database/Tests/Helpers/FTestHelpers.m @@ -0,0 +1,132 @@ +/* + * 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 "FTestHelpers.h" +#import "FConstants.h" +#import "FIRApp.h" +#import "FIROptions.h" +#import "FIRDatabaseConfig_Private.h" +#import "FTestAuthTokenGenerator.h" + +@implementation FTestHelpers + ++ (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds { + NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate]; + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:seconds]; + NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate]; + NSTimeInterval currentTime; + + for (currentTime = [NSDate timeIntervalSinceReferenceDate]; + !predicate() && currentTime < timeoutTime; + currentTime = [NSDate timeIntervalSinceReferenceDate]) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; + } + + NSTimeInterval finish = [NSDate timeIntervalSinceReferenceDate]; + + NSAssert(currentTime <= timeoutTime, @"Timed out"); + + return (finish - start); +} + ++ (NSArray*) getRandomNodes:(int)num persistence:(BOOL)persistence { + static dispatch_once_t pred = 0; + static NSMutableArray *persistenceRefs = nil; + static NSMutableArray *noPersistenceRefs = nil; + dispatch_once(&pred, ^{ + persistenceRefs = [[NSMutableArray alloc] init]; + noPersistenceRefs = [[NSMutableArray alloc] init]; + // Uncomment the following line to run tests against a background thread + //[Firebase setDispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + }); + + NSMutableArray *refs = (persistence) ? persistenceRefs : noPersistenceRefs; + + id<FAuthTokenProvider> authTokenProvider = [FAuthTokenProvider authTokenProviderForApp:[FIRApp defaultApp]]; + + while (num > refs.count) { + NSString *sessionIdentifier = [NSString stringWithFormat:@"test-config-%@persistence-%lu", (persistence) ? @"" : @"no-", refs.count]; + FIRDatabaseConfig *config = [[FIRDatabaseConfig alloc] initWithSessionIdentifier:sessionIdentifier authTokenProvider:authTokenProvider]; + config.persistenceEnabled = persistence; + FIRDatabaseReference * ref = [[FIRDatabaseReference alloc] initWithConfig:config]; + [refs addObject:ref]; + } + + NSMutableArray* results = [[NSMutableArray alloc] init]; + NSString* name = nil; + for (int i = 0; i < num; ++i) { + FIRDatabaseReference * ref = [refs objectAtIndex:i]; + if (!name) { + name = [ref childByAutoId].key; + } + [results addObject:[ref child:name]]; + } + return results; +} + +// Helpers ++ (FIRDatabaseReference *) getRandomNode { + NSArray* refs = [self getRandomNodes:1 persistence:YES]; + return [refs objectAtIndex:0]; +} + ++ (FIRDatabaseReference *) getRandomNodeWithoutPersistence { + NSArray* refs = [self getRandomNodes:1 persistence:NO]; + return refs[0]; +} + ++ (FTupleFirebase *) getRandomNodePair { + NSArray* refs = [self getRandomNodes:2 persistence:YES]; + + FTupleFirebase* tuple = [[FTupleFirebase alloc] init]; + tuple.one = [refs objectAtIndex:0]; + tuple.two = [refs objectAtIndex:1]; + + return tuple; +} + ++ (FTupleFirebase *) getRandomNodePairWithoutPersistence { + NSArray* refs = [self getRandomNodes:2 persistence:NO]; + + FTupleFirebase* tuple = [[FTupleFirebase alloc] init]; + tuple.one = refs[0]; + tuple.two = refs[1]; + + return tuple; +} + ++ (FTupleFirebase *) getRandomNodeTriple { + NSArray* refs = [self getRandomNodes:3 persistence:YES]; + FTupleFirebase* triple = [[FTupleFirebase alloc] init]; + triple.one = [refs objectAtIndex:0]; + triple.two = [refs objectAtIndex:1]; + triple.three = [refs objectAtIndex:2]; + + return triple; +} + ++ (id<FNode>)leafNodeOfSize:(NSUInteger)size { + NSMutableString *string = [NSMutableString string]; + NSString *pattern = @"abdefghijklmopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + for (NSUInteger i = 0; i < size - pattern.length; i = i + pattern.length) { + [string appendString:pattern]; + } + NSUInteger remainingLength = size - string.length; + [string appendString:[pattern substringToIndex:remainingLength]]; + return [FSnapshotUtilities nodeFrom:string]; +} + +@end diff --git a/Example/Database/Tests/Helpers/FTupleEventTypeString.h b/Example/Database/Tests/Helpers/FTupleEventTypeString.h new file mode 100644 index 0000000..adcb4a0 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTupleEventTypeString.h @@ -0,0 +1,33 @@ +/* + * 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 <Foundation/Foundation.h> +#import "FIRDataEventType.h" +#import "FIRDatabaseReference.h" +#import "FTypedefs.h" + +@interface FTupleEventTypeString : NSObject + +- (id)initWithFirebase:(FIRDatabaseReference *)f withEvent:(FIRDataEventType)evt withString:(NSString *)str; +- (BOOL) isEqualTo:(FTupleEventTypeString *)other; + +@property (nonatomic, strong) FIRDatabaseReference * firebase; +@property (readwrite) FIRDataEventType eventType; +@property (nonatomic, strong) NSString* string; +@property (nonatomic, copy) fbt_void_void vvcallback; +@property (nonatomic) BOOL initialized; + +@end diff --git a/Example/Database/Tests/Helpers/FTupleEventTypeString.m b/Example/Database/Tests/Helpers/FTupleEventTypeString.m new file mode 100644 index 0000000..4cb3df2 --- /dev/null +++ b/Example/Database/Tests/Helpers/FTupleEventTypeString.m @@ -0,0 +1,53 @@ +/* + * 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 "FTupleEventTypeString.h" + +@implementation FTupleEventTypeString + +@synthesize firebase; +@synthesize eventType; +@synthesize string; +@synthesize vvcallback; +@synthesize initialized; + +- (id)initWithFirebase:(FIRDatabaseReference *)f withEvent:(FIRDataEventType)evt withString:(NSString *)str; +{ + self = [super init]; + if (self) { + self.firebase = f; + self.eventType = evt; + self.string = str; + self.initialized = NO; + } + return self; +} + +- (NSString *) description { + return [NSString stringWithFormat:@"%@ %@ (%zd)", self.firebase, self.string, self.eventType]; +} + +- (BOOL) isEqualTo:(FTupleEventTypeString *)other { + BOOL stringsEqual = NO; + if (self.string == nil && other.string == nil) { + stringsEqual = YES; + } else if (self.string != nil && other.string != nil) { + stringsEqual = [self.string isEqualToString:other.string]; + } + return self.eventType == other.eventType && stringsEqual && [[self.firebase description] isEqualToString:[other.firebase description]]; +} + +@end diff --git a/Example/Database/Tests/Helpers/SenTest+FWaiter.h b/Example/Database/Tests/Helpers/SenTest+FWaiter.h new file mode 100644 index 0000000..81556df --- /dev/null +++ b/Example/Database/Tests/Helpers/SenTest+FWaiter.h @@ -0,0 +1,26 @@ +/* + * 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 <XCTest/XCTest.h> + +@interface XCTest (FWaiter) + +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate; +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate description:(NSString*)desc; +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds; +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds description:(NSString*)desc; + +@end diff --git a/Example/Database/Tests/Helpers/SenTest+FWaiter.m b/Example/Database/Tests/Helpers/SenTest+FWaiter.m new file mode 100644 index 0000000..4c5c854 --- /dev/null +++ b/Example/Database/Tests/Helpers/SenTest+FWaiter.m @@ -0,0 +1,57 @@ +/* + * 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 "SenTest+FWaiter.h" +#import "FTestContants.h" + +@implementation XCTestCase (FWaiter) + +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate { + return [self waitUntil:predicate timeout:kFirebaseTestWaitUntilTimeout description:nil]; +} + +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate description:(NSString*)desc { + return [self waitUntil:predicate timeout:kFirebaseTestWaitUntilTimeout description:desc]; +} + +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds { + return [self waitUntil:predicate timeout:seconds description:nil]; +} + +- (NSTimeInterval) waitUntil:(BOOL (^)())predicate timeout:(NSTimeInterval)seconds description:(NSString*)desc { + NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate]; + NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:seconds]; + NSTimeInterval timeoutTime = [timeoutDate timeIntervalSinceReferenceDate]; + NSTimeInterval currentTime; + + for (currentTime = [NSDate timeIntervalSinceReferenceDate]; + !predicate() && currentTime < timeoutTime; + currentTime = [NSDate timeIntervalSinceReferenceDate]) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; + } + + NSTimeInterval finish = [NSDate timeIntervalSinceReferenceDate]; + if (currentTime > timeoutTime) { + if (desc != nil) { + XCTFail("Timed out on: %@", desc); + } else { + XCTFail("Timed out"); + } + } + return (finish - start); +} + +@end |