| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538 |
- /*
- * 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 "FEventTester.h"
- #import "FIRDatabaseConfig_Private.h"
- #import "FIRDatabaseQuery_Private.h"
- #import "FTestHelpers.h"
- #import "FTupleEventTypeString.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 = [[[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.authTokenProvider = [[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
|