mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
* AppAttestation initial public API * Run ./scripts/style.sh * Copyright * Cleanup * Cleanup * Get token forcing refresh method * Formatting * Forcing refresh samples * Interop introduced * Code samples for Interop * Run ./scripts/style.sh * Cleanup * TODOs and Copirights * Fix build * Supported iOS versions * Remove unnecessary bridging header * Run ./scripts/style.sh * [WIP] DeviceCheckAttestationProvider as a subspec * cleanup * Add memory attributes to readonly properties * Move FIRDeviceCheckAttestationProvider.h to private headers * TODO for `FirebaseAppAttestation/Core` name * Remove memory attributes from FIRAppAttestationToken properties * FIRAppAttestationTests.testSetAttestationProviderFactory introduced * FIRAppAttestationToken initializer * Basic FIRLibrary implementation and FIRAppAttestationProviderFactory ingestion. * Fix * FIRAppAttestationToken+Interop.h * TODOs * Tests: reset FIRApps * GHA for App Attestation * Run ./scripts/style.sh * Style fixes. * Run ./scripts/style.sh * TODO * Nullable initializer and type annpotations * TODOs * TODOs * TODO * Fix spec and allow warnings * AppAttestation initial public API * Run ./scripts/style.sh * Copyright * Cleanup * Cleanup * Get token forcing refresh method * Formatting * Forcing refresh samples * Interop introduced * Code samples for Interop * Run ./scripts/style.sh * Cleanup * TODOs and Copirights * Fix build * Supported iOS versions * Remove unnecessary bridging header * Run ./scripts/style.sh * [WIP] DeviceCheckAttestationProvider as a subspec * cleanup * Add memory attributes to readonly properties * Move FIRDeviceCheckAttestationProvider.h to private headers * TODO for `FirebaseAppAttestation/Core` name * Remove memory attributes from FIRAppAttestationToken properties * FIRAppAttestationTests.testSetAttestationProviderFactory introduced * FIRAppAttestationToken initializer * Basic FIRLibrary implementation and FIRAppAttestationProviderFactory ingestion. * Fix * FIRAppAttestationToken+Interop.h * TODOs * Tests: reset FIRApps * Run ./scripts/style.sh * Style fixes. * Run ./scripts/style.sh * TODO * Nullable initializer and type annpotations * TODOs * TODOs * TODO * Revert FirestoreDecoder.swift formatting. * Basic implementation of FIRDeviceCheckAttestationProvider and tests (#626) * DeviceCheck attestation provider sceleton * DeviceCheck attestation provider sceleton * Basic FIRDeviceCheckAttestationProviderTests * Run ./scripts/style.sh * FIRAppAttestStorage implementation and tests (#627) * FIRDeviceCheckAttestationProvider designated init * FIRAppAttestStorage API and basic tests. * FIRAppAttestStoredToken: NSSecureCoding implementation * WIP Storage * Add `accessGroup` param to `FIRAppAttestStorage` * FIRAppAttestStorage implementation * Use constants * FIRAppAttestStorage API doc * Coding style and typos. * Code formatting * Attestation Token caching (#630) * Storage instance and looging * AppAttestation caching * Tests and cleanup * Cleanup * Rename App Attestation -> App Check (#631) * Rename App Attestation -> App Check * Run ./scripts/style.sh * Update GHA * Default Custom Provider (#633) * AppCheck: remove not unnecessary public API (#634) * AppCheck: remove not unnecessary public API * Run ./scripts/style.sh * AppCheck: DeviceCheck token server API (#635) * Generic HTTP request handling * [draft] AppCheck: device check HTTP API and simple tests * Parse response * Cleanup * AppCheck: API errors and tests (#636) * AppCheck errors * API service tests and fixes * Cleanup * Run ./scripts/style.sh * Naming * AppCheck: Register device check provider factory as a default (#637) * AppCheck errors * API service tests and fixes * Cleanup * Run ./scripts/style.sh * AppCheck: register FIRDeviceCheckProvider as a component * Naming * Registre device check provider factory as a default * FIRDeviceCheckProvider tests * AppCheck: API communication common for all providers moved to FIRAppCheckAPIService (#640) * FIRAppCheckAPIService introduced * Move URL seession stub to FIRURLSessionOCMockStub * Run ./scripts/style.sh * Move approximate date comparing to FIRDateTestUtils * compose request in FIRAppCheckAPIService * FIRAppCheckAPIServiceTests * Use FIRAppCheckAPIService in FIRDeviceCheckProvider * Cleanup * DeviceCheck mock server integration and tests (#686) * DeviceCheck mock server integration and tests * TODO * Update FIROptions.h import * Temporary add FirebaseAppCheck to skip import validation * TestUtilities moved to SharedTestUtilities * FirebaseAppCheck.podspec: TestUtilities->SharedTestUtilities * SwiftPM: add OCMock dependency to SharedTestUtilities * FIRURLSessionOCMockStub: #import "OCMock.h" * App Check: Firebase 7 fixes (#715) * AppCheck Firebase 7 * FirebaseCore repo-relative imports * Fix FIRAppCheckStorage.m compilation * Repo relative imports * linebreak * AppCheck: debug FAC token provider (#716) * AppCheck Firebase 7 * FirebaseCore repo-relative imports * Fix FIRAppCheckStorage.m compilation * Repo relative imports * linebreak * Remove FIRAppCheckDefaultCustomProvider * FIRAppCheckDebugProvider introduced * FIRAppCheckDebugProviderTests * style * Print locally generated debug token to console * Fix imports * mullability and line breaks * typo * Comments * TODO * AppCheck: merge into single spec (package) (#717) * Standard package folder structure * Move interop header into AppCheck sources folder * Combine public headers in the standard dir * Private headers to internal * Podspec * Headers import udpdate * style * Fix header imports * Enable check_imports.swift and fix imports * AppCheck: server request adjustments (#718) * Wrap DeviceCheck token into JSON * AppChek - remove static_framework requirement * WIP * Update default base URL * Set content-type header * Check for empty response data * Fix tests * Style and formatting * Podspec - revert wrong change and remove static_framework * Assign error to error pointer safely. * AppCheck: primitive test app (#719) * Remove static_framework requirement * WIP: FIRAppCheckTestApp introduced * Delete .plist * Copyright * Remove empty test targets * pod deintegrate * Remove test targets from Podfile * formatting * Storage: integration with AppCheck (#720) * Storage: add AppCheck interop dependency * Inject AppCheck into FIRStorageTokenAuthorizer * initial AppCheck integration * Introduce AppCheck fake * AppCheck fake implementation * FIRStorageTokenAuthorizer tests and fixes * @"Authorization" header constant * Log AppCheck error. * Cleanup * FIRStorageTokenAuthorizerTests: helpers * TODO * Cleanup * AppCheck: separate 2P and 3P API (#724) * FIRAppCheckInterop: remove a convenience method. * Rename: FIRAppCheckTokenInterop->FIRAppCheckTokenResultInterop * FIRAppCheckTokenResult introduced * Update FIRAppCheckTokenHandlerInterop, FIRAppCheckTokenResultInterop and tests * style * Update token result * Update storage integration * Adjust check_imports.swift for test files * Don't use internal AppCheck classes in Storage tests * style * Rename token to tokenResult in 2P API * Add private Core headers to app check sources * Fix storage log message code format * token clarification comments in API docs * Rename token->tokenResult * AppCheck cleanup (#737) * Fix public headers import * Cleanup imported test utils * FirebaseAppCheck podspec: revert git URL * AppCheck: Debug token to AppCheck token exchange API (#739) * GUL: make NSURLSession promise extension public * Use NSURLSession+GULPromises to send requests * style * Move FIRAppCheckToken+APIResponse to Core dir * rename * FIRAppCheckDebugProviderAPIService introduced * Move async token parsing API to FIRAppCheckToken extension * Move token exchange response handling to FIRAppCheckAPIService. Tests WIP. * Move API response parsing tests to FIRAppCheckAPIServiceTests * FIRAppCheckDebugProviderAPIServiceTests * Cleanup * style * App Check debug provider: integrate with exchange API (#743) * Move FIROption validation to FIRAppCheckValidator * FIRAppCheckDebugProvider token exchange and tests * Fix FIRAppCheckDebugProviderFactoryTests * style * App Check test app debug provider fix (#747) * App Check: minor touchups (#750) * Update dummy token value * Print local debug token to logs * Require GoogleUtilities v7.2 and above * Print debug token on init * Don't print debug token again * App Check: use prod environment (#751) * App Check: use prod backend by default. * Update token expiration threshold * App Check SwiftPM support (#753) * AppCheck SwiftPM package definition * Promise header import fix * OCMock header fix * Fix fixture dependent tests * Exclude Keychain dependent tests * License * ./scripts/style.sh * Cleanup * Format with proper SwiftFormat version * `.process` resources. * Swift tests target * Cleanup and code formatting * App Check: token auto-refresh (#754) * FIRAppCheckTokenRefresher API defined * FIRAppCheckTokenRefresher implementation draft * [WIP] Token refresher integration * Token refresher integration * FIRAppCheckTests: token refresher tests * FIRAppCheckTests: additional tests cases * FIRAppCheckTimer introduced * FIRAppCheckTokenRefresher: use FIRAppCheckTimer instead of the dispatch timer. * FIRFakeTimer * FIRAppCheckTokenRefresherTests testInitialRefresh * FIRAppCheckTokenRefresherTests: testNextRefreshOnRefreshSuccess * ./scripts/style.sh * Exponential backoff * Style and comments * Move token refresh files to a separate folder * FIRAppCheckTimerTests * FIRAppCheck: use expicit class in init * FIRAppCheckDebugProviderFactory Swift name * FIRAppCheckTokenRefresher: Increment/decrement directly * App Check fixes (#755) * Fix backoff: no backoff for the initial request * Use eager initialization * FIRAppCheckTokenRefresher: don't refresh until handler set * FIRAppCheckIntegrationTests: disable token refresher * Fix testInitialRefresh test * style * FIRAppCheckIntegrationTests fix * Temporary exclude App Check from platfrom logging (#756) * App Check: send app check SDK version only in the header (#757) * App Check: store FAC token per app ID (#758) * App Check: store FAC token per app ID * update podspec * API docs * FIRAppCheck initWithApp tests * AppCheckCustomProvideApp (#760) * Functions: App Check integration (#762) * Functions: app check API added to FUNContextProvider * FUNContextProviderTests * FUNContextProvider app check support * ./scripts/style.sh * FIRFunctions: pass FAC token as HTTP header * Fix SPM * Fix Firebase-Package scheme bad merge * Cleanup * RTDB: pass FAC token to web socket (#763) * RTDB: pass FAC token to web socket * FIRAppCheckFake default init * Rename FAuthTokenProvider to FIRDatabaseConnectionContext; integrate with App Check * Pass App Check token in the HTTP header * Rename * Fix existing tests * TODO * ./scripts/style.sh * Context provider: assume separate FAC token change listener * FIRDatabaseConnectionContextProviderTests * Remove unused files * Copy NSMutableURLRequest * Cleanup * ./scripts/style.sh * FIRDatabaseConnectionContextProvider: sync callback when no auth or app check * Cleanup * Rename "authToken" leftovers to "context" * Fix renaming * Pass missing headers to NSURLSessionWebSocketTask * Cleanup * add a name to TODO * App Check: token update notifications (#764) * AppCheck: token did change API and tests * notification implementation and fixes * Run ./scripts/style.sh * Update comments * RTDB: send app check token updates to keep connection open (#765) * Pass AppCheck instance as an object into the the FAC token update notification * FIRDatabaseConnectionContext: add FAC token change listener API * formatting * Send FAC token updates to open connection * More informative empty response message * refreshAppCheckToken fixes * Remove assertion * Don't modify state * forceTokensRefresh -> forceTokenRefreshes * Call listeners on [FIRDatabaseQuery sharedQueue] * App Check API cleanup and docs (#766) * AppCheck public API and docs cleanup * More API docs * Fix compilation errors * typos and wording * Make summary different from description (pod lib lint warning) * port app check workflow changes from another branch * pod lib lint: remove --allow-warnings * App Check cleanup (#767) * Cleanup debug code and outdated TODOs * Fix FIRDeviceCheckProvider availablility for SPM * Fix availability in tests * Fix availability in tests * Make sure os requirements are consistent for tests specs * Migrate v1alpha URLs to v1beta * Migrate v1alpha URLs to v1beta * Run ./scripts/style.sh * Functions tests: fix Xcode 11 warnings (#7931) * Functions tests: fix Xcode 11 warnings * style * App Check: token auto-refresh opt-out API (#769) * FIRAppCheckSettings introduced (WIP) * FIRAppCheckSettingsTests * Style * Make FIRAppCheckSettings instance per-app. * FIRAppCheckTokenRefresher: use FIRAppCheckSettings.isTokenAutoRefreshEnabled (WIP) * Tests * Add public instance getters * Fix analyzer warnings * Fix testspec OS versions * FIRAppCheckTokenRefresher expiration date update; tests * FIRAppCheck.isTokenAutoRefreshEnabled implementation and tests * style * FIRAppCheck: Use specific types for variables in the default init * More precise comments * missing symbol * FIRAppCheckSettings: remove defensive conditions from init. Co-authored-by: Nick Cooke <nickcooke@google.com> Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com>
1546 lines
53 KiB
Objective-C
1546 lines
53 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 "FirebaseDatabase/Tests/Integration/FTransactionTest.h"
|
|
#import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
|
|
#import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
|
|
#import "FirebaseDatabase/Tests/Helpers/FEventTester.h"
|
|
#import "FirebaseDatabase/Tests/Helpers/FTestHelpers.h"
|
|
#import "FirebaseDatabase/Tests/Helpers/FTupleEventTypeString.h"
|
|
|
|
// HACK used by testUnsentTransactionsAreNotCancelledOnDisconnect to return one bad token and then a
|
|
// nil token.
|
|
@interface FIROneBadTokenProvider : NSObject <FIRDatabaseConnectionContextProvider> {
|
|
BOOL firstFetch;
|
|
}
|
|
@end
|
|
|
|
@implementation FIROneBadTokenProvider
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
firstFetch = YES;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)fetchContextForcingRefresh:(BOOL)forceRefresh
|
|
withCallback:(void (^)(FIRDatabaseConnectionContext *_Nullable context,
|
|
NSError *_Nullable error))callback {
|
|
// Simulate delay
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)),
|
|
[FIRDatabaseQuery sharedQueue], ^{
|
|
if (self->firstFetch) {
|
|
self->firstFetch = NO;
|
|
__auto_type context =
|
|
[[FIRDatabaseConnectionContext alloc] initWithAuthToken:@"bad-token"
|
|
appCheckToken:nil];
|
|
callback(context, nil);
|
|
} else {
|
|
callback(nil, nil);
|
|
}
|
|
});
|
|
}
|
|
|
|
- (void)listenForAuthTokenChanges:(fbt_void_nsstring)listener {
|
|
}
|
|
|
|
- (void)listenForAppCheckTokenChanges:(nonnull fbt_void_nsstring)listener {
|
|
}
|
|
|
|
@end
|
|
@implementation FTransactionTest
|
|
|
|
- (void)testNewValueIsImmediatelyVisible {
|
|
FIRDatabaseReference *node = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL runOnce = NO;
|
|
[[node child:@"foo"] runTransactionBlock:^(FIRMutableData *currentValue) {
|
|
runOnce = YES;
|
|
[currentValue setValue:@42];
|
|
return [FIRTransactionResult successWithValue:currentValue];
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return runOnce;
|
|
}];
|
|
|
|
__block BOOL ready = NO;
|
|
[[node child:@"foo"]
|
|
observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
if (!ready) {
|
|
NSNumber *val = [snapshot value];
|
|
XCTAssertTrue([val isEqualToNumber:@42], @"Got value set in transaction");
|
|
ready = YES;
|
|
}
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
}
|
|
|
|
- (void)testNonAbortedTransactionSetsCommittedToTrueInCallback {
|
|
FIRDatabaseReference *node = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL done = NO;
|
|
[[node child:@"foo"]
|
|
runTransactionBlock:^(FIRMutableData *currentValue) {
|
|
[currentValue setValue:@42];
|
|
return [FIRTransactionResult successWithValue:currentValue];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should not have aborted");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
- (void)testAbortedTransactionSetsCommittedToFalseInCallback {
|
|
FIRDatabaseReference *node = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL done = NO;
|
|
[[node child:@"foo"]
|
|
runTransactionBlock:^(FIRMutableData *currentValue) {
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertFalse(committed, @"Should have aborted");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
- (void)testBugTestSetDataReconnectDoTransactionThatAbortsOnceDataArrivesVerifyCorrectEvents {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *reader = refs.one;
|
|
|
|
__block BOOL dataWritten = NO;
|
|
[[reader child:@"foo"] setValue:@42
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
dataWritten = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return dataWritten;
|
|
}];
|
|
|
|
FIRDatabaseReference *writer = refs.two;
|
|
__block int eventsReceived = 0;
|
|
[[writer child:@"foo"]
|
|
observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
if (eventsReceived == 0) {
|
|
NSString *val = [snapshot value];
|
|
XCTAssertTrue([val isEqualToString:@"temp value"],
|
|
@"Got initial transaction value");
|
|
} else if (eventsReceived == 1) {
|
|
NSNumber *val = [snapshot value];
|
|
XCTAssertTrue([val isEqualToNumber:@42], @"Got hidden original value");
|
|
} else {
|
|
XCTFail(@"Too many events");
|
|
}
|
|
eventsReceived++;
|
|
}];
|
|
|
|
[[writer child:@"foo"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id current = [currentData value];
|
|
if (current == [NSNull null]) {
|
|
[currentData setValue:@"temp value"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else {
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertFalse(committed, @"This transaction should never commit");
|
|
XCTAssertTrue(error == nil, @"This transaction should not have an error");
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return eventsReceived == 2;
|
|
}];
|
|
}
|
|
|
|
- (void)testUseTransactionToCreateANodeMakeSureExactlyOneEventIsReceived {
|
|
FIRDatabaseReference *node = [FTestHelpers getRandomNode];
|
|
|
|
__block int events = 0;
|
|
__block BOOL done = NO;
|
|
|
|
[[node child:@"a"] observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
events++;
|
|
if (events > 1) {
|
|
XCTFail(@"Too many events");
|
|
}
|
|
}];
|
|
|
|
[[node child:@"a"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@42];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done && events == 1;
|
|
}];
|
|
}
|
|
|
|
- (void)testUseTransactionToUpdateTwoExistingChildNodesMakeSureEventsAreOnlyRaisedForChangedNode {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *node1 = [refs.one child:@"foo"];
|
|
FIRDatabaseReference *node2 = [refs.two child:@"foo"];
|
|
|
|
__block BOOL ready = NO;
|
|
[[node1 child:@"a"] setValue:@42];
|
|
[[node1 child:@"b"] setValue:@42
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
ready = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
|
|
FEventTester *et = [[FEventTester alloc] initFrom:self];
|
|
NSArray *expect = @[
|
|
[[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"a"]
|
|
withEvent:FIRDataEventTypeValue
|
|
withString:nil],
|
|
[[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"b"]
|
|
withEvent:FIRDataEventTypeValue
|
|
withString:nil]
|
|
];
|
|
|
|
[et addLookingFor:expect];
|
|
[et wait];
|
|
|
|
expect = @[ [[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"b"]
|
|
withEvent:FIRDataEventTypeValue
|
|
withString:nil] ];
|
|
|
|
[et addLookingFor:expect];
|
|
|
|
ready = NO;
|
|
[node2
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
NSDictionary *toSet = @{@"a" : @42, @"b" : @87};
|
|
[currentData setValue:toSet];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
ready = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
|
|
[et wait];
|
|
}
|
|
|
|
- (void)testTransactionOnlyCalledOnceWhenInitializingAnEmptyNode {
|
|
FIRDatabaseReference *node = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL updateCalled = NO;
|
|
[node runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
XCTAssertTrue(val == [NSNull null], @"Should be no value here to start with");
|
|
if (updateCalled) {
|
|
XCTFail(@"Should not be called again");
|
|
}
|
|
updateCalled = YES;
|
|
[currentData setValue:@{@"a" : @5, @"b" : @6}];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return updateCalled;
|
|
}];
|
|
}
|
|
|
|
- (void)testSecondTransactionGetsRunImmediatelyOnPreviousOutputAndOnlyRunsOnce {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *ref1 = refs.one;
|
|
FIRDatabaseReference *ref2 = refs.two;
|
|
|
|
__block BOOL firstRun = NO;
|
|
__block BOOL firstDone = NO;
|
|
__block BOOL secondRun = NO;
|
|
__block BOOL secondDone = NO;
|
|
|
|
[ref1
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
XCTAssertFalse(firstRun, @"Should not be run twice");
|
|
firstRun = YES;
|
|
[currentData setValue:@42];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should not fail");
|
|
firstDone = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return firstRun;
|
|
}];
|
|
|
|
[ref1
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
XCTAssertFalse(secondRun, @"Should only run once");
|
|
secondRun = YES;
|
|
NSNumber *val = [currentData value];
|
|
XCTAssertTrue([val isEqualToNumber:@42], @"Should see result of last transaction");
|
|
[currentData setValue:@84];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should not fail");
|
|
secondDone = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return secondRun;
|
|
}];
|
|
|
|
__block FIRDataSnapshot *snap = nil;
|
|
[ref1 observeSingleEventOfType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
snap = snapshot;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return snap != nil;
|
|
}];
|
|
|
|
XCTAssertTrue([[snap value] isEqualToNumber:@84], @"Should get updated value");
|
|
|
|
[self waitUntil:^BOOL {
|
|
return firstDone && secondDone;
|
|
}];
|
|
|
|
snap = nil;
|
|
[ref2 observeSingleEventOfType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
snap = snapshot;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return snap != nil;
|
|
}];
|
|
|
|
XCTAssertTrue([[snap value] isEqualToNumber:@84], @"Should get updated value");
|
|
}
|
|
|
|
// The js test, "Set() cancels pending transactions and re-runs affected transactions.", does not
|
|
// cleanly port to ios due to everything being asynchronous. Rather than attempt to mitigate the
|
|
// various race conditions inherent in a port, I'm adding tests to cover the specific behaviors
|
|
// wrapped up in that one test.
|
|
|
|
- (void)testSetCancelsPendingTransaction {
|
|
FIRDatabaseReference *node = [FTestHelpers getRandomNode];
|
|
|
|
__block FIRDataSnapshot *nodeSnap = nil;
|
|
__block FIRDataSnapshot *nodeFooSnap = nil;
|
|
|
|
[node observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
nodeSnap = snapshot;
|
|
}];
|
|
|
|
[[node child:@"foo"] observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
nodeFooSnap = snapshot;
|
|
}];
|
|
|
|
__block BOOL firstDone = NO;
|
|
__block BOOL secondDone = NO;
|
|
__block BOOL firstRun = NO;
|
|
|
|
[[node child:@"foo"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
XCTAssertFalse(firstRun, @"Should only run once");
|
|
firstRun = YES;
|
|
[currentData setValue:@42];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should not fail");
|
|
firstDone = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return nodeFooSnap != nil;
|
|
}];
|
|
|
|
XCTAssertTrue([[nodeFooSnap value] isEqualToNumber:@42], @"Got first value");
|
|
|
|
[node
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@{@"foo" : @84, @"bar" : @1}];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertFalse(committed, @"This should not ever be committed");
|
|
secondDone = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return nodeSnap != nil;
|
|
}];
|
|
|
|
[[node child:@"foo"] setValue:@0];
|
|
}
|
|
|
|
// It's difficult to force a transaction re-run on ios, since everything is async. There is also an
|
|
// outstanding case that prevents this test from being before a connection is established (#1981)
|
|
/*
|
|
- (void) testSetRerunsAffectedTransactions {
|
|
|
|
Firebase* node = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL ready = NO;
|
|
[[node.parent child:@".info/connected"] observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) { ready = [[snapshot value] boolValue];
|
|
}];
|
|
[self waitUntil:^BOOL{
|
|
return ready;
|
|
}];
|
|
|
|
__block FIRDataSnapshot* nodeSnap = nil;
|
|
|
|
[node observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
|
|
nodeSnap = snapshot;
|
|
NSLog(@"SNAP value: %@", [snapshot value]);
|
|
}];
|
|
|
|
__block BOOL firstDone = NO;
|
|
__block BOOL secondDone = NO;
|
|
__block BOOL firstRun = NO;
|
|
__block int secondCount = 0;
|
|
__block BOOL setDone = NO;
|
|
|
|
[node runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
STAssertFalse(firstRun, @"Should only run once");
|
|
firstRun = YES;
|
|
[currentData setValue:@42];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
STAssertTrue(committed, @"Should not fail");
|
|
firstDone = YES;
|
|
}];
|
|
|
|
[[node child:@"bar"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
NSLog(@"RUNNING TRANSACTION");
|
|
secondCount++;
|
|
id val = [currentData value];
|
|
if (secondCount == 1) {
|
|
STAssertTrue(val == [NSNull null], @"Should not have a value");
|
|
[currentData setValue:@"first"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else if (secondCount == 2) {
|
|
NSLog(@"val: %@", val);
|
|
STAssertTrue(val == [NSNull null], @"Should not have a value");
|
|
[currentData setValue:@"second"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else {
|
|
STFail(@"Called too many times");
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
} andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
STAssertTrue(committed, @"Should eventually be committed");
|
|
secondDone = YES;
|
|
}];
|
|
|
|
[[node child:@"foo"] setValue:@0 andCompletionBlock:^(NSError *error) {
|
|
setDone = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL{
|
|
return setDone;
|
|
}];
|
|
|
|
NSDictionary* expected = @{@"bar": @"second", @"foo": @0};
|
|
STAssertTrue([[nodeSnap value] isEqualToDictionary:expected], @"Got last value");
|
|
|
|
STAssertTrue(secondCount == 2, @"Should have re-run second transaction");
|
|
}*/
|
|
|
|
- (void)testTransactionSetSetWorks {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL done = NO;
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
XCTAssertTrue(val == [NSNull null], @"Initial data should be null");
|
|
[currentData setValue:@"hi!"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error == nil, @"Should not be an error");
|
|
XCTAssertTrue(committed, @"Should commit");
|
|
done = YES;
|
|
}];
|
|
|
|
[ref setValue:@"foo"];
|
|
[ref setValue:@"bar"];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
- (void)testPriorityIsNotPreservedWhenSettingData {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
|
|
__block FIRDataSnapshot *snap = nil;
|
|
[ref observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
snap = snapshot;
|
|
}];
|
|
|
|
[ref setValue:@"test" andPriority:@5];
|
|
|
|
__block BOOL ready = NO;
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@"new value"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
ready = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
|
|
id val = [snap value];
|
|
id pri = [snap priority];
|
|
XCTAssertTrue(pri == [NSNull null], @"Got priority");
|
|
XCTAssertTrue([val isEqualToString:@"new value"], @"Get new value");
|
|
}
|
|
|
|
// Skipping test with nested transactions. Everything is async on ios, so new transactions just get
|
|
// placed in a queue
|
|
|
|
- (void)testResultSnapshotIsPassedToOnComplete {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *ref1 = refs.one;
|
|
FIRDatabaseReference *ref2 = refs.two;
|
|
|
|
__block BOOL done = NO;
|
|
[ref1
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
if (val == [NSNull null]) {
|
|
[currentData setValue:@"hello!"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else {
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should commit");
|
|
XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
// do it again for the aborted case
|
|
|
|
done = NO;
|
|
[ref1
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
if (val == [NSNull null]) {
|
|
[currentData setValue:@"hello!"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else {
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertFalse(committed, @"Should not commit");
|
|
XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
// do it again on a fresh connection, for the aborted case
|
|
done = NO;
|
|
[ref2
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
if (val == [NSNull null]) {
|
|
[currentData setValue:@"hello!"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else {
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertFalse(committed, @"Should not commit");
|
|
XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
- (void)testTransactionAbortsAfter25Retries {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
|
|
[ref.repo setHijackHash:YES];
|
|
|
|
__block int tries = 0;
|
|
__block BOOL done = NO;
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
XCTAssertTrue(tries < 25, @"Should not be more than 25 tries");
|
|
tries++;
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error != nil, @"Should fail, too many retries");
|
|
XCTAssertFalse(committed, @"Should not commit");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
[ref.repo setHijackHash:NO];
|
|
}
|
|
|
|
- (void)testSetShouldCancelSentTransactionsThatComeBackAsDatastale {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *ref1 = refs.one;
|
|
FIRDatabaseReference *ref2 = refs.two;
|
|
|
|
__block BOOL ready = NO;
|
|
[ref1 setValue:@5
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
ready = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
|
|
ready = NO;
|
|
[ref2
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
XCTAssertTrue(val == [NSNull null], @"No current value");
|
|
[currentData setValue:@72];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error != nil, @"Should abort");
|
|
XCTAssertFalse(committed, @"Should not commit");
|
|
ready = YES;
|
|
}];
|
|
|
|
[ref2 setValue:@32];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
}
|
|
|
|
- (void)testUpdateShouldNotCancelUnrelatedTransactions {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL fooTransactionDone = NO;
|
|
__block BOOL barTransactionDone = NO;
|
|
|
|
[self waitForCompletionOf:[ref child:@"foo"] setValue:@"oldValue"];
|
|
|
|
[ref.repo setHijackHash:YES];
|
|
|
|
// This transaction should get cancelled as we update "foo" later on.
|
|
[[ref child:@"foo"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@72];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error != nil, @"Should abort");
|
|
XCTAssertFalse(committed, @"Should not commit");
|
|
fooTransactionDone = YES;
|
|
}];
|
|
|
|
// This transaction should not get cancelled since we don't update "bar".
|
|
[[ref child:@"bar"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@72];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
// Note: In rare cases, this might get aborted since failed transactions (forced by
|
|
// setHijackHash) are only retried 25 times. If we hit this limit before we stop hijacking
|
|
// the hash below, this test will flake.
|
|
XCTAssertTrue(error == nil, @"Should not abort");
|
|
XCTAssertTrue(committed, @"Should commit");
|
|
barTransactionDone = YES;
|
|
}];
|
|
|
|
NSDictionary *udpateData = @{
|
|
@"foo" : @"newValue",
|
|
@"boo" : @"newValue",
|
|
@"doo/foo" : @"newValue",
|
|
@"loo" : @{@"doo" : @{@"boo" : @"newValue"}}
|
|
};
|
|
|
|
[self waitForCompletionOf:ref updateChildValues:udpateData];
|
|
XCTAssertTrue(fooTransactionDone, "Should have gotten cancelled before the update");
|
|
XCTAssertFalse(barTransactionDone, "Should run after the update");
|
|
[ref.repo setHijackHash:NO];
|
|
|
|
WAIT_FOR(barTransactionDone);
|
|
}
|
|
|
|
- (void)testTransactionOnWackyUnicode {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *ref1 = refs.one;
|
|
FIRDatabaseReference *ref2 = refs.two;
|
|
|
|
__block BOOL ready = NO;
|
|
[ref1 setValue:@"♜♞♝♛♚♝♞♜"
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
ready = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
|
|
ready = NO;
|
|
[ref2
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
if (val != [NSNull null]) {
|
|
XCTAssertTrue([val isEqualToString:@"♜♞♝♛♚♝♞♜"], @"Got crazy unicode");
|
|
}
|
|
[currentData setValue:@"♖♘♗♕♔♗♘♖"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error == nil, @"Should not abort");
|
|
XCTAssertTrue(committed, @"Should commit");
|
|
ready = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
}
|
|
|
|
- (void)testImmediatelyAbortedTransactions {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
|
|
[ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
return [FIRTransactionResult abort];
|
|
}];
|
|
|
|
__block BOOL ready = NO;
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error == nil, @"No error occurred, we just aborted");
|
|
XCTAssertFalse(committed, @"Should not commit");
|
|
ready = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return ready;
|
|
}];
|
|
}
|
|
|
|
- (void)testAddingToAnArrayWithATransaction {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
|
|
__block BOOL done = NO;
|
|
[ref setValue:@[ @"cat", @"horse" ]
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
done = NO;
|
|
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
if (val != [NSNull null]) {
|
|
NSArray *arr = val;
|
|
NSMutableArray *toSet = [arr mutableCopy];
|
|
[toSet addObject:@"dog"];
|
|
[currentData setValue:toSet];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else {
|
|
[currentData setValue:@[ @"dog" ]];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should commit");
|
|
NSArray *val = [snapshot value];
|
|
NSArray *expected = @[ @"cat", @"horse", @"dog" ];
|
|
XCTAssertTrue([val isEqualToArray:expected], @"Got whole array");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
- (void)testMergedTransactionsHaveCorrectSnapshotInOnComplete {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *node1 = refs.one;
|
|
FIRDatabaseReference *node2 = refs.two;
|
|
|
|
__block BOOL done = NO;
|
|
[node1 setValue:@{@"a" : @0}
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
__block BOOL transaction1Done = NO;
|
|
__block BOOL transaction2Done = NO;
|
|
|
|
[node2
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
if (val != [NSNull null]) {
|
|
XCTAssertTrue([@{@"a" : @0} isEqualToDictionary:val], @"Got initial data");
|
|
}
|
|
[currentData setValue:@{@"a" : @1}];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should commit");
|
|
XCTAssertTrue([snapshot.key isEqualToString:node2.key], @"Correct snapshot name");
|
|
NSDictionary *val = [snapshot value];
|
|
// Per new behavior, will include the accepted value of the transaction, if it was
|
|
// successful.
|
|
NSDictionary *expected = @{@"a" : @1};
|
|
XCTAssertTrue([val isEqualToDictionary:expected], @"Got final result");
|
|
transaction1Done = YES;
|
|
}];
|
|
|
|
[[node2 child:@"a"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id val = [currentData value];
|
|
if (val != [NSNull null]) {
|
|
XCTAssertTrue([@1 isEqualToNumber:val], @"Got initial data");
|
|
}
|
|
[currentData setValue:@2];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(committed, @"Should commit");
|
|
XCTAssertTrue([snapshot.key isEqualToString:@"a"], @"Correct snapshot name");
|
|
NSNumber *val = [snapshot value];
|
|
NSNumber *expected = @2;
|
|
XCTAssertTrue([val isEqualToNumber:expected], @"Got final result");
|
|
transaction2Done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return transaction1Done && transaction2Done;
|
|
}];
|
|
}
|
|
|
|
// Skipping two tests on nested calls. Since iOS uses a work queue, nested calls don't actually
|
|
// happen synchronously, so they aren't problematic
|
|
|
|
- (void)testPendingTransactionsAreCancelledOnDisconnect {
|
|
FIRDatabaseConfig *cfg = [FTestHelpers configForName:@"pending-transactions"];
|
|
FIRDatabaseReference *ref = [[[FTestHelpers databaseForConfig:cfg] reference] childByAutoId];
|
|
|
|
__block BOOL done = NO;
|
|
[[ref child:@"a"] setValue:@"initial"
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
done = NO;
|
|
[[ref child:@"b"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@"new"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertFalse(committed, @"Should not commit");
|
|
XCTAssertTrue(error != nil, @"Should be an error");
|
|
done = YES;
|
|
}];
|
|
|
|
[FRepoManager interrupt:cfg];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
// cleanup
|
|
[FRepoManager interrupt:cfg];
|
|
[FRepoManager disposeRepos:cfg];
|
|
}
|
|
|
|
- (void)testTransactionWithoutLocalEvents1 {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
NSMutableArray *values = [[NSMutableArray alloc] init];
|
|
[ref observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
[values addObject:[snapshot value]];
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
// get initial data
|
|
return values.count > 0;
|
|
}];
|
|
|
|
__block BOOL done = NO;
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@"hello!"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error == nil, @"Should not be an error");
|
|
XCTAssertTrue(committed, @"Committed");
|
|
XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"got correct snapshot");
|
|
done = YES;
|
|
}
|
|
withLocalEvents:NO];
|
|
|
|
NSArray *expected = @[ [NSNull null] ];
|
|
XCTAssertTrue([values isEqualToArray:expected], @"Should not have gotten any values yet");
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
expected = @[ [NSNull null], @"hello!" ];
|
|
XCTAssertTrue([values isEqualToArray:expected], @"Should have the new value now");
|
|
}
|
|
|
|
- (void)testTransactionWithoutLocalEvents2 {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *ref1 = refs.one;
|
|
FIRDatabaseReference *ref2 = refs.two;
|
|
int SETS = 4;
|
|
|
|
[ref1.repo setHijackHash:YES];
|
|
|
|
NSMutableArray *events = [[NSMutableArray alloc] init];
|
|
[ref1 setValue:@0];
|
|
[ref1 observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
[events addObject:[snapshot value]];
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return events.count > 0;
|
|
}];
|
|
|
|
NSArray *expected = @[ @0 ];
|
|
XCTAssertTrue([events isEqualToArray:expected], @"Got initial set");
|
|
|
|
__block int retries = 0;
|
|
__block BOOL done = NO;
|
|
[ref1
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
retries++;
|
|
id val = [currentData value];
|
|
NSNumber *num = @0;
|
|
if (val != [NSNull null]) {
|
|
num = val;
|
|
}
|
|
int eventCount = [num intValue];
|
|
if (eventCount == SETS - 1) {
|
|
[ref1.repo setHijackHash:NO];
|
|
}
|
|
|
|
[currentData setValue:@"txn result"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertTrue(error == nil, @"Should not be an error");
|
|
XCTAssertTrue(committed, @"Committed");
|
|
XCTAssertTrue([[snapshot value] isEqualToString:@"txn result"], @"got correct snapshot");
|
|
done = YES;
|
|
}
|
|
withLocalEvents:NO];
|
|
|
|
// Meanwhile, do sets from the second connection
|
|
for (int i = 0; i < SETS; ++i) {
|
|
__block BOOL setDone = NO;
|
|
[ref2 setValue:[NSNumber numberWithInt:i]
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
setDone = YES;
|
|
}];
|
|
[self waitUntil:^BOOL {
|
|
return setDone;
|
|
}];
|
|
}
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
|
|
XCTAssertTrue(retries > 0, @"Transaction should have retried");
|
|
XCTAssertEqualObjects([events lastObject], @"txn result",
|
|
@"Final value matches expected value from txn");
|
|
}
|
|
|
|
// Skipping test of calling transaction from value callback. Since all api calls are async on iOS,
|
|
// nested calls are not a problem.
|
|
|
|
- (void)testTransactionRevertsDataWhenAddADeeperListen {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *ref1 = refs.one;
|
|
FIRDatabaseReference *ref2 = refs.two;
|
|
|
|
__block BOOL done = NO;
|
|
[[ref1 child:@"y"] setValue:@"test"
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
[ref2 runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
if (currentData.value == [NSNull null]) {
|
|
[[currentData childDataByAppendingPath:@"x"] setValue:@5];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
} else {
|
|
return [FIRTransactionResult abort];
|
|
}
|
|
}];
|
|
|
|
[[ref2 child:@"y"] observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
if ([snapshot.value isEqual:@"test"]) {
|
|
done = YES;
|
|
}
|
|
}];
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
- (void)testTransactionWithIntegerKeys {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
__block BOOL done = NO;
|
|
NSDictionary *toSet = @{@"1" : @1, @"5" : @5, @"10" : @10, @"20" : @20};
|
|
[ref setValue:toSet
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@42];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"Error should be nil.");
|
|
XCTAssertTrue(committed, @"Transaction should have committed.");
|
|
done = YES;
|
|
}];
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
// https://app.asana.com/0/5673976843758/9259161251948
|
|
- (void)testBubbleAppTransactionBug {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
__block BOOL done = NO;
|
|
[[ref child:@"a"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@1];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
|
|
}];
|
|
|
|
[[ref child:@"a"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
NSNumber *val = currentData.value;
|
|
NSNumber *new = [ NSNumber numberWithInt : (val.intValue + 42) ];
|
|
[currentData setValue:new];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
|
|
}];
|
|
|
|
[[ref child:@"b"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@7];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
|
|
}];
|
|
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
NSNumber *a = [currentData childDataByAppendingPath:@"a"].value;
|
|
NSNumber *b = [currentData childDataByAppendingPath:@"b"].value;
|
|
NSNumber *new = [ NSNumber numberWithInt : a.intValue + b.intValue ];
|
|
[currentData setValue:new];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"Error should be nil.");
|
|
XCTAssertTrue(committed, @"Committed should be true.");
|
|
XCTAssertEqualObjects(@50, snapshot.value, @"Result should be 50.");
|
|
done = YES;
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
// If we have cached data, transactions shouldn't run on null.
|
|
- (void)testTransactionsAreRunInitiallyOnCurrentlyCachedData {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
id initialData = @{@"a" : @"a-val", @"b" : @"b-val"};
|
|
__block BOOL done = NO;
|
|
__weak FIRDatabaseReference *weakRef = ref;
|
|
[ref setValue:initialData
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *r) {
|
|
[weakRef observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
[weakRef runTransactionBlock:^FIRTransactionResult *(
|
|
FIRMutableData *currentData) {
|
|
XCTAssertEqualObjects(currentData.value, initialData,
|
|
@"Should be initial data.");
|
|
done = YES;
|
|
return [FIRTransactionResult abort];
|
|
}];
|
|
}];
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done;
|
|
}];
|
|
}
|
|
|
|
- (void)testMultipleLevels {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
__block BOOL done = NO;
|
|
|
|
[ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}];
|
|
|
|
[[ref child:@"a"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}];
|
|
|
|
[[ref child:@"b"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}];
|
|
|
|
[ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
}
|
|
|
|
- (void)testLocalServerValuesEventuallyButNotImmediatelyMatchServerWithTxns {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *writer = refs.one;
|
|
FIRDatabaseReference *reader = refs.two;
|
|
__block int done = 0;
|
|
|
|
NSMutableArray *readSnaps = [[NSMutableArray alloc] init];
|
|
NSMutableArray *writeSnaps = [[NSMutableArray alloc] init];
|
|
|
|
[reader observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
if ([snapshot value] != [NSNull null]) {
|
|
[readSnaps addObject:snapshot];
|
|
if (readSnaps.count == 1) {
|
|
done += 1;
|
|
}
|
|
}
|
|
}];
|
|
|
|
[writer observeEventType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
if ([snapshot value] != [NSNull null]) {
|
|
[writeSnaps addObject:snapshot];
|
|
if (writeSnaps.count == 2) {
|
|
done += 1;
|
|
}
|
|
}
|
|
}];
|
|
|
|
[writer
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:[FIRServerValue timestamp]];
|
|
[currentData setPriority:[FIRServerValue timestamp]];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
|
|
}];
|
|
|
|
[self waitUntil:^BOOL {
|
|
return done == 2;
|
|
}];
|
|
|
|
XCTAssertEqual((unsigned long)[readSnaps count], (unsigned long)1,
|
|
@"Should have received one snapshot on reader");
|
|
XCTAssertEqual((unsigned long)[writeSnaps count], (unsigned long)2,
|
|
@"Should have received two snapshots on writer");
|
|
|
|
FIRDataSnapshot *firstReadSnap = [readSnaps objectAtIndex:0];
|
|
FIRDataSnapshot *firstWriteSnap = [writeSnaps objectAtIndex:0];
|
|
FIRDataSnapshot *secondWriteSnap = [writeSnaps objectAtIndex:1];
|
|
|
|
NSNumber *now = [NSNumber numberWithDouble:round([[NSDate date] timeIntervalSince1970] * 1000)];
|
|
XCTAssertTrue([now doubleValue] - [firstWriteSnap.value doubleValue] < 2000,
|
|
@"Should have received a local event with a value close to timestamp");
|
|
XCTAssertTrue([now doubleValue] - [firstWriteSnap.priority doubleValue] < 2000,
|
|
@"Should have received a local event with a priority close to timestamp");
|
|
XCTAssertTrue([now doubleValue] - [secondWriteSnap.value doubleValue] < 2000,
|
|
@"Should have received a server event with a value close to timestamp");
|
|
XCTAssertTrue([now doubleValue] - [secondWriteSnap.priority doubleValue] < 2000,
|
|
@"Should have received a server event with a priority close to timestamp");
|
|
|
|
XCTAssertFalse([firstWriteSnap value] == [secondWriteSnap value],
|
|
@"Initial and future writer values should be different");
|
|
XCTAssertFalse([firstWriteSnap priority] == [secondWriteSnap priority],
|
|
@"Initial and future writer priorities should be different");
|
|
XCTAssertEqualObjects(firstReadSnap.value, secondWriteSnap.value,
|
|
@"Eventual reader and writer values should be equal");
|
|
XCTAssertEqualObjects(firstReadSnap.priority, secondWriteSnap.priority,
|
|
@"Eventual reader and writer priorities should be equal");
|
|
}
|
|
|
|
- (void)testTransactionWithQueryListen {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
__block BOOL done = NO;
|
|
|
|
[ref setValue:@{@"a" : @1, @"b" : @2}
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
[[ref queryLimitedToFirst:1]
|
|
observeEventType:FIRDataEventTypeChildAdded
|
|
andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
|
|
}
|
|
withCancelBlock:^(NSError *error){
|
|
}];
|
|
|
|
[[ref child:@"a"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"This transaction should not have an error");
|
|
XCTAssertTrue(committed, @"Should not have aborted");
|
|
XCTAssertEqualObjects([snapshot value], @1,
|
|
@"Transaction value should match initial set");
|
|
done = YES;
|
|
}];
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
}
|
|
|
|
- (void)testTransactionDoesNotPickUpCachedDataFromPreviousOnce {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *me = refs.one;
|
|
FIRDatabaseReference *other = refs.two;
|
|
__block BOOL done = NO;
|
|
|
|
[me setValue:@"not null"
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
done = NO;
|
|
|
|
[me observeSingleEventOfType:FIRDataEventTypeValue
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
done = NO;
|
|
|
|
[other setValue:[NSNull null]
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
done = NO;
|
|
|
|
[me
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id current = [currentData value];
|
|
if (current == [NSNull null]) {
|
|
[currentData setValue:@"it was null!"];
|
|
} else {
|
|
[currentData setValue:@"it was not null!"];
|
|
}
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"This transaction should not have an error");
|
|
XCTAssertTrue(committed, @"Should not have aborted");
|
|
XCTAssertEqualObjects([snapshot value], @"it was null!",
|
|
@"Transaction value should match remote null set");
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
}
|
|
|
|
- (void)testTransactionDoesNotPickUpCachedDataFromPreviousTransaction {
|
|
FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
|
|
FIRDatabaseReference *me = refs.one;
|
|
FIRDatabaseReference *other = refs.two;
|
|
__block BOOL done = NO;
|
|
|
|
[me
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@"not null"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"This transaction should not have an error");
|
|
XCTAssertTrue(committed, @"Should not have aborted");
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
done = NO;
|
|
|
|
[other setValue:[NSNull null]
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
done = NO;
|
|
|
|
[me
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id current = [currentData value];
|
|
if (current == [NSNull null]) {
|
|
[currentData setValue:@"it was null!"];
|
|
} else {
|
|
[currentData setValue:@"it was not null!"];
|
|
}
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"This transaction should not have an error");
|
|
XCTAssertTrue(committed, @"Should not have aborted");
|
|
XCTAssertEqualObjects([snapshot value], @"it was null!",
|
|
@"Transaction value should match remote null set");
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
}
|
|
|
|
- (void)testTransactionOnQueriedLocationDoesntRunInitiallyOnNull {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
__block BOOL txnDone = NO;
|
|
|
|
[self waitForCompletionOf:[ref childByAutoId] setValue:@{@"a" : @1, @"b" : @2}];
|
|
|
|
[[ref queryLimitedToFirst:1]
|
|
observeEventType:FIRDataEventTypeChildAdded
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
[snapshot.ref
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
id expected = @{@"a" : @1, @"b" : @2};
|
|
XCTAssertEqualObjects(currentData.value, expected, @"");
|
|
[currentData setValue:[NSNull null]];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"");
|
|
XCTAssertTrue(committed, @"");
|
|
XCTAssertEqualObjects(snapshot.value, [NSNull null], @"");
|
|
txnDone = YES;
|
|
}];
|
|
}];
|
|
|
|
WAIT_FOR(txnDone);
|
|
}
|
|
|
|
- (void)testTransactionsRaiseCorrectChildChangedEventsOnQueries {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
__block BOOL txnDone = NO;
|
|
NSMutableArray *snapshots = [[NSMutableArray alloc] init];
|
|
|
|
[self waitForCompletionOf:ref setValue:@{@"foo" : @{@"value" : @1}}];
|
|
|
|
FIRDatabaseQuery *query = [ref queryEndingAtValue:@(DBL_MIN)];
|
|
|
|
[query observeEventType:FIRDataEventTypeChildAdded
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
[snapshots addObject:snapshot];
|
|
}];
|
|
|
|
[query observeEventType:FIRDataEventTypeChildChanged
|
|
withBlock:^(FIRDataSnapshot *snapshot) {
|
|
[snapshots addObject:snapshot];
|
|
}];
|
|
|
|
[[ref child:@"foo"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[[currentData childDataByAppendingPath:@"value"] setValue:@2];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"");
|
|
XCTAssertTrue(committed, @"");
|
|
txnDone = YES;
|
|
}
|
|
withLocalEvents:NO];
|
|
|
|
WAIT_FOR(txnDone);
|
|
|
|
XCTAssertTrue(snapshots.count == 2, @"");
|
|
FIRDataSnapshot *addedSnapshot = snapshots[0];
|
|
XCTAssertEqualObjects(addedSnapshot.key, @"foo", @"");
|
|
XCTAssertEqualObjects(addedSnapshot.value, @{@"value" : @1}, @"");
|
|
|
|
FIRDataSnapshot *changedSnapshot = snapshots[1];
|
|
XCTAssertEqualObjects(changedSnapshot.key, @"foo", @"");
|
|
XCTAssertEqualObjects(changedSnapshot.value, @{@"value" : @2}, @"");
|
|
}
|
|
|
|
- (void)testTransactionsUseLocalMerges {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
__block BOOL txnDone = NO;
|
|
[ref updateChildValues:@{@"foo" : @"bar"}];
|
|
|
|
[[ref child:@"foo"]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
XCTAssertEqualObjects(currentData.value, @"bar",
|
|
@"Transaction value matches local updates");
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error, @"");
|
|
XCTAssertTrue(committed, @"");
|
|
txnDone = YES;
|
|
}];
|
|
|
|
WAIT_FOR(txnDone);
|
|
}
|
|
|
|
// See https://app.asana.com/0/15566422264127/23303789496881
|
|
- (void)testOutOfOrderRemoveWritesAreHandledCorrectly {
|
|
FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
|
|
[ref setValue:@{@"foo" : @"bar"}];
|
|
[ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@"transaction-1"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}];
|
|
[ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@"transaction-2"];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}];
|
|
__block BOOL done = NO;
|
|
// This will trigger an abort of the transaction which should not cause the client to crash
|
|
[ref updateChildValues:@{@"qux" : @"quu"}
|
|
withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
|
|
XCTAssertNil(error);
|
|
done = YES;
|
|
}];
|
|
|
|
WAIT_FOR(done);
|
|
}
|
|
|
|
- (void)testUnsentTransactionsAreNotCancelledOnDisconnect {
|
|
// Hack: To trigger us to disconnect before restoring state, we inject a bad auth token.
|
|
// In real-world usage the much more common case is that we get redirected to a different
|
|
// server, but that's harder to manufacture from a test.
|
|
NSString *configName = @"testUnsentTransactionsAreNotCancelledOnDisconnect";
|
|
FIRDatabaseConfig *config = [FTestHelpers configForName:configName];
|
|
config.contextProvider = [[FIROneBadTokenProvider alloc] init];
|
|
|
|
// Queue a transaction offline.
|
|
FIRDatabaseReference *root = [[FTestHelpers databaseForConfig:config] reference];
|
|
[root.database goOffline];
|
|
__block BOOL done = NO;
|
|
[[root childByAutoId]
|
|
runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
|
|
[currentData setValue:@0];
|
|
return [FIRTransactionResult successWithValue:currentData];
|
|
}
|
|
andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
|
|
XCTAssertNil(error);
|
|
XCTAssertTrue(committed);
|
|
done = YES;
|
|
}];
|
|
|
|
[root.database goOnline];
|
|
WAIT_FOR(done);
|
|
}
|
|
|
|
@end
|