| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382 |
- /*
- * 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 "FTransactionTest.h"
- #import "FTestHelpers.h"
- #import "FEventTester.h"
- #import "FTupleEventTypeString.h"
- #import "FIRDatabaseQuery_Private.h"
- #import "FIRDatabaseConfig_Private.h"
- // HACK used by testUnsentTransactionsAreNotCancelledOnDisconnect to return one bad token and then a nil token.
- @interface FIROneBadTokenProvider : NSObject <FAuthTokenProvider> {
- BOOL firstFetch;
- }
- @end
- @implementation FIROneBadTokenProvider
- - (instancetype) init {
- self = [super init];
- if (self) {
- firstFetch = YES;
- }
- return self;
- }
- - (void) fetchTokenForcingRefresh:(BOOL)forceRefresh withCallback:(fbt_void_nsstring_nserror)callback {
- // Simulate delay
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)), [FIRDatabaseQuery sharedQueue], ^{
- if (self->firstFetch) {
- self->firstFetch = NO;
- callback(@"bad-token", nil);
- } else {
- callback(nil, nil);
- }
- });
- }
- - (void) listenForTokenChanges:(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 = [[[FIRDatabaseReference alloc] initWithConfig:cfg] 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.authTokenProvider = [[FIROneBadTokenProvider alloc] init];
- // Queue a transaction offline.
- FIRDatabaseReference *root = [[FIRDatabaseReference alloc] initWithConfig:config];
- [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
|