| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545 |
- /*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "FirebaseDatabase/Tests/Integration/FTransactionTest.h"
- #import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
- #import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
- #import "FirebaseDatabase/Tests/Helpers/FEventTester.h"
- #import "FirebaseDatabase/Tests/Helpers/FTestHelpers.h"
- #import "FirebaseDatabase/Tests/Helpers/FTupleEventTypeString.h"
- // HACK used by testUnsentTransactionsAreNotCancelledOnDisconnect to return one bad token and then a
- // nil token.
- @interface FIROneBadTokenProvider : NSObject <FIRDatabaseConnectionContextProvider> {
- BOOL firstFetch;
- }
- @end
- @implementation FIROneBadTokenProvider
- - (instancetype)init {
- self = [super init];
- if (self) {
- firstFetch = YES;
- }
- return self;
- }
- - (void)fetchContextForcingRefresh:(BOOL)forceRefresh
- withCallback:(void (^)(FIRDatabaseConnectionContext *_Nullable context,
- NSError *_Nullable error))callback {
- // Simulate delay
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)),
- [FIRDatabaseQuery sharedQueue], ^{
- if (self->firstFetch) {
- self->firstFetch = NO;
- __auto_type context =
- [[FIRDatabaseConnectionContext alloc] initWithAuthToken:@"bad-token"
- appCheckToken:nil];
- callback(context, nil);
- } else {
- callback(nil, nil);
- }
- });
- }
- - (void)listenForAuthTokenChanges:(fbt_void_nsstring)listener {
- }
- - (void)listenForAppCheckTokenChanges:(nonnull fbt_void_nsstring)listener {
- }
- @end
- @implementation FTransactionTest
- - (void)testNewValueIsImmediatelyVisible {
- FIRDatabaseReference *node = [FTestHelpers getRandomNode];
- __block BOOL runOnce = NO;
- [[node child:@"foo"] runTransactionBlock:^(FIRMutableData *currentValue) {
- runOnce = YES;
- [currentValue setValue:@42];
- return [FIRTransactionResult successWithValue:currentValue];
- }];
- [self waitUntil:^BOOL {
- return runOnce;
- }];
- __block BOOL ready = NO;
- [[node child:@"foo"]
- observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- if (!ready) {
- NSNumber *val = [snapshot value];
- XCTAssertTrue([val isEqualToNumber:@42], @"Got value set in transaction");
- ready = YES;
- }
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- }
- - (void)testNonAbortedTransactionSetsCommittedToTrueInCallback {
- FIRDatabaseReference *node = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- [[node child:@"foo"]
- runTransactionBlock:^(FIRMutableData *currentValue) {
- [currentValue setValue:@42];
- return [FIRTransactionResult successWithValue:currentValue];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should not have aborted");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- - (void)testAbortedTransactionSetsCommittedToFalseInCallback {
- FIRDatabaseReference *node = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- [[node child:@"foo"]
- runTransactionBlock:^(FIRMutableData *currentValue) {
- return [FIRTransactionResult abort];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertFalse(committed, @"Should have aborted");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- - (void)testBugTestSetDataReconnectDoTransactionThatAbortsOnceDataArrivesVerifyCorrectEvents {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *reader = refs.one;
- __block BOOL dataWritten = NO;
- [[reader child:@"foo"] setValue:@42
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- dataWritten = YES;
- }];
- [self waitUntil:^BOOL {
- return dataWritten;
- }];
- FIRDatabaseReference *writer = refs.two;
- __block int eventsReceived = 0;
- [[writer child:@"foo"]
- observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- if (eventsReceived == 0) {
- NSString *val = [snapshot value];
- XCTAssertTrue([val isEqualToString:@"temp value"],
- @"Got initial transaction value");
- } else if (eventsReceived == 1) {
- NSNumber *val = [snapshot value];
- XCTAssertTrue([val isEqualToNumber:@42], @"Got hidden original value");
- } else {
- XCTFail(@"Too many events");
- }
- eventsReceived++;
- }];
- [[writer child:@"foo"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id current = [currentData value];
- if (current == [NSNull null]) {
- [currentData setValue:@"temp value"];
- return [FIRTransactionResult successWithValue:currentData];
- } else {
- return [FIRTransactionResult abort];
- }
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertFalse(committed, @"This transaction should never commit");
- XCTAssertTrue(error == nil, @"This transaction should not have an error");
- }];
- [self waitUntil:^BOOL {
- return eventsReceived == 2;
- }];
- }
- - (void)testUseTransactionToCreateANodeMakeSureExactlyOneEventIsReceived {
- FIRDatabaseReference *node = [FTestHelpers getRandomNode];
- __block int events = 0;
- __block BOOL done = NO;
- [[node child:@"a"] observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- events++;
- if (events > 1) {
- XCTFail(@"Too many events");
- }
- }];
- [[node child:@"a"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@42];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done && events == 1;
- }];
- }
- - (void)testUseTransactionToUpdateTwoExistingChildNodesMakeSureEventsAreOnlyRaisedForChangedNode {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *node1 = [refs.one child:@"foo"];
- FIRDatabaseReference *node2 = [refs.two child:@"foo"];
- __block BOOL ready = NO;
- [[node1 child:@"a"] setValue:@42];
- [[node1 child:@"b"] setValue:@42
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- ready = YES;
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- FEventTester *et = [[FEventTester alloc] initFrom:self];
- NSArray *expect = @[
- [[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"a"]
- withEvent:FIRDataEventTypeValue
- withString:nil],
- [[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"b"]
- withEvent:FIRDataEventTypeValue
- withString:nil]
- ];
- [et addLookingFor:expect];
- [et wait];
- expect = @[ [[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"b"]
- withEvent:FIRDataEventTypeValue
- withString:nil] ];
- [et addLookingFor:expect];
- ready = NO;
- [node2
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- NSDictionary *toSet = @{@"a" : @42, @"b" : @87};
- [currentData setValue:toSet];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- ready = YES;
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- [et wait];
- }
- - (void)testTransactionOnlyCalledOnceWhenInitializingAnEmptyNode {
- FIRDatabaseReference *node = [FTestHelpers getRandomNode];
- __block BOOL updateCalled = NO;
- [node runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- XCTAssertTrue(val == [NSNull null], @"Should be no value here to start with");
- if (updateCalled) {
- XCTFail(@"Should not be called again");
- }
- updateCalled = YES;
- [currentData setValue:@{@"a" : @5, @"b" : @6}];
- return [FIRTransactionResult successWithValue:currentData];
- }];
- [self waitUntil:^BOOL {
- return updateCalled;
- }];
- }
- - (void)testSecondTransactionGetsRunImmediatelyOnPreviousOutputAndOnlyRunsOnce {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *ref1 = refs.one;
- FIRDatabaseReference *ref2 = refs.two;
- __block BOOL firstRun = NO;
- __block BOOL firstDone = NO;
- __block BOOL secondRun = NO;
- __block BOOL secondDone = NO;
- [ref1
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- XCTAssertFalse(firstRun, @"Should not be run twice");
- firstRun = YES;
- [currentData setValue:@42];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should not fail");
- firstDone = YES;
- }];
- [self waitUntil:^BOOL {
- return firstRun;
- }];
- [ref1
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- XCTAssertFalse(secondRun, @"Should only run once");
- secondRun = YES;
- NSNumber *val = [currentData value];
- XCTAssertTrue([val isEqualToNumber:@42], @"Should see result of last transaction");
- [currentData setValue:@84];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should not fail");
- secondDone = YES;
- }];
- [self waitUntil:^BOOL {
- return secondRun;
- }];
- __block FIRDataSnapshot *snap = nil;
- [ref1 observeSingleEventOfType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- snap = snapshot;
- }];
- [self waitUntil:^BOOL {
- return snap != nil;
- }];
- XCTAssertTrue([[snap value] isEqualToNumber:@84], @"Should get updated value");
- [self waitUntil:^BOOL {
- return firstDone && secondDone;
- }];
- snap = nil;
- [ref2 observeSingleEventOfType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- snap = snapshot;
- }];
- [self waitUntil:^BOOL {
- return snap != nil;
- }];
- XCTAssertTrue([[snap value] isEqualToNumber:@84], @"Should get updated value");
- }
- // The js test, "Set() cancels pending transactions and re-runs affected transactions.", does not
- // cleanly port to ios due to everything being asynchronous. Rather than attempt to mitigate the
- // various race conditions inherent in a port, I'm adding tests to cover the specific behaviors
- // wrapped up in that one test.
- - (void)testSetCancelsPendingTransaction {
- FIRDatabaseReference *node = [FTestHelpers getRandomNode];
- __block FIRDataSnapshot *nodeSnap = nil;
- __block FIRDataSnapshot *nodeFooSnap = nil;
- [node observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- nodeSnap = snapshot;
- }];
- [[node child:@"foo"] observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- nodeFooSnap = snapshot;
- }];
- __block BOOL firstDone = NO;
- __block BOOL secondDone = NO;
- __block BOOL firstRun = NO;
- [[node child:@"foo"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- XCTAssertFalse(firstRun, @"Should only run once");
- firstRun = YES;
- [currentData setValue:@42];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should not fail");
- firstDone = YES;
- }];
- [self waitUntil:^BOOL {
- return nodeFooSnap != nil;
- }];
- XCTAssertTrue([[nodeFooSnap value] isEqualToNumber:@42], @"Got first value");
- [node
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@{@"foo" : @84, @"bar" : @1}];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertFalse(committed, @"This should not ever be committed");
- secondDone = YES;
- }];
- [self waitUntil:^BOOL {
- return nodeSnap != nil;
- }];
- [[node child:@"foo"] setValue:@0];
- }
- // It's difficult to force a transaction re-run on ios, since everything is async. There is also an
- // outstanding case that prevents this test from being before a connection is established (#1981)
- /*
- - (void) testSetRerunsAffectedTransactions {
- Firebase* node = [FTestHelpers getRandomNode];
- __block BOOL ready = NO;
- [[node.parent child:@".info/connected"] observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) { ready = [[snapshot value] boolValue];
- }];
- [self waitUntil:^BOOL{
- return ready;
- }];
- __block FIRDataSnapshot* nodeSnap = nil;
- [node observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
- nodeSnap = snapshot;
- NSLog(@"SNAP value: %@", [snapshot value]);
- }];
- __block BOOL firstDone = NO;
- __block BOOL secondDone = NO;
- __block BOOL firstRun = NO;
- __block int secondCount = 0;
- __block BOOL setDone = NO;
- [node runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- STAssertFalse(firstRun, @"Should only run once");
- firstRun = YES;
- [currentData setValue:@42];
- return [FIRTransactionResult successWithValue:currentData];
- } andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- STAssertTrue(committed, @"Should not fail");
- firstDone = YES;
- }];
- [[node child:@"bar"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- NSLog(@"RUNNING TRANSACTION");
- secondCount++;
- id val = [currentData value];
- if (secondCount == 1) {
- STAssertTrue(val == [NSNull null], @"Should not have a value");
- [currentData setValue:@"first"];
- return [FIRTransactionResult successWithValue:currentData];
- } else if (secondCount == 2) {
- NSLog(@"val: %@", val);
- STAssertTrue(val == [NSNull null], @"Should not have a value");
- [currentData setValue:@"second"];
- return [FIRTransactionResult successWithValue:currentData];
- } else {
- STFail(@"Called too many times");
- return [FIRTransactionResult abort];
- }
- } andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- STAssertTrue(committed, @"Should eventually be committed");
- secondDone = YES;
- }];
- [[node child:@"foo"] setValue:@0 andCompletionBlock:^(NSError *error) {
- setDone = YES;
- }];
- [self waitUntil:^BOOL{
- return setDone;
- }];
- NSDictionary* expected = @{@"bar": @"second", @"foo": @0};
- STAssertTrue([[nodeSnap value] isEqualToDictionary:expected], @"Got last value");
- STAssertTrue(secondCount == 2, @"Should have re-run second transaction");
- }*/
- - (void)testTransactionSetSetWorks {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- XCTAssertTrue(val == [NSNull null], @"Initial data should be null");
- [currentData setValue:@"hi!"];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error == nil, @"Should not be an error");
- XCTAssertTrue(committed, @"Should commit");
- done = YES;
- }];
- [ref setValue:@"foo"];
- [ref setValue:@"bar"];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- - (void)testPriorityIsNotPreservedWhenSettingData {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block FIRDataSnapshot *snap = nil;
- [ref observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- snap = snapshot;
- }];
- [ref setValue:@"test" andPriority:@5];
- __block BOOL ready = NO;
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@"new value"];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- ready = YES;
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- id val = [snap value];
- id pri = [snap priority];
- XCTAssertTrue(pri == [NSNull null], @"Got priority");
- XCTAssertTrue([val isEqualToString:@"new value"], @"Get new value");
- }
- // Skipping test with nested transactions. Everything is async on ios, so new transactions just get
- // placed in a queue
- - (void)testResultSnapshotIsPassedToOnComplete {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *ref1 = refs.one;
- FIRDatabaseReference *ref2 = refs.two;
- __block BOOL done = NO;
- [ref1
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- if (val == [NSNull null]) {
- [currentData setValue:@"hello!"];
- return [FIRTransactionResult successWithValue:currentData];
- } else {
- return [FIRTransactionResult abort];
- }
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should commit");
- XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- // do it again for the aborted case
- done = NO;
- [ref1
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- if (val == [NSNull null]) {
- [currentData setValue:@"hello!"];
- return [FIRTransactionResult successWithValue:currentData];
- } else {
- return [FIRTransactionResult abort];
- }
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertFalse(committed, @"Should not commit");
- XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- // do it again on a fresh connection, for the aborted case
- done = NO;
- [ref2
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- if (val == [NSNull null]) {
- [currentData setValue:@"hello!"];
- return [FIRTransactionResult successWithValue:currentData];
- } else {
- return [FIRTransactionResult abort];
- }
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertFalse(committed, @"Should not commit");
- XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- - (void)testTransactionAbortsAfter25Retries {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- [ref.repo setHijackHash:YES];
- __block int tries = 0;
- __block BOOL done = NO;
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- XCTAssertTrue(tries < 25, @"Should not be more than 25 tries");
- tries++;
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error != nil, @"Should fail, too many retries");
- XCTAssertFalse(committed, @"Should not commit");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- [ref.repo setHijackHash:NO];
- }
- - (void)testSetShouldCancelSentTransactionsThatComeBackAsDatastale {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *ref1 = refs.one;
- FIRDatabaseReference *ref2 = refs.two;
- __block BOOL ready = NO;
- [ref1 setValue:@5
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- ready = YES;
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- ready = NO;
- [ref2
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- XCTAssertTrue(val == [NSNull null], @"No current value");
- [currentData setValue:@72];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error != nil, @"Should abort");
- XCTAssertFalse(committed, @"Should not commit");
- ready = YES;
- }];
- [ref2 setValue:@32];
- [self waitUntil:^BOOL {
- return ready;
- }];
- }
- - (void)testUpdateShouldNotCancelUnrelatedTransactions {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL fooTransactionDone = NO;
- __block BOOL barTransactionDone = NO;
- [self waitForCompletionOf:[ref child:@"foo"] setValue:@"oldValue"];
- [ref.repo setHijackHash:YES];
- // This transaction should get cancelled as we update "foo" later on.
- [[ref child:@"foo"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@72];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error != nil, @"Should abort");
- XCTAssertFalse(committed, @"Should not commit");
- fooTransactionDone = YES;
- }];
- // This transaction should not get cancelled since we don't update "bar".
- [[ref child:@"bar"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@72];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- // Note: In rare cases, this might get aborted since failed transactions (forced by
- // setHijackHash) are only retried 25 times. If we hit this limit before we stop hijacking
- // the hash below, this test will flake.
- XCTAssertTrue(error == nil, @"Should not abort");
- XCTAssertTrue(committed, @"Should commit");
- barTransactionDone = YES;
- }];
- NSDictionary *udpateData = @{
- @"foo" : @"newValue",
- @"boo" : @"newValue",
- @"doo/foo" : @"newValue",
- @"loo" : @{@"doo" : @{@"boo" : @"newValue"}}
- };
- [self waitForCompletionOf:ref updateChildValues:udpateData];
- XCTAssertTrue(fooTransactionDone, "Should have gotten cancelled before the update");
- XCTAssertFalse(barTransactionDone, "Should run after the update");
- [ref.repo setHijackHash:NO];
- WAIT_FOR(barTransactionDone);
- }
- - (void)testTransactionOnWackyUnicode {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *ref1 = refs.one;
- FIRDatabaseReference *ref2 = refs.two;
- __block BOOL ready = NO;
- [ref1 setValue:@"♜♞♝♛♚♝♞♜"
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- ready = YES;
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- ready = NO;
- [ref2
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- if (val != [NSNull null]) {
- XCTAssertTrue([val isEqualToString:@"♜♞♝♛♚♝♞♜"], @"Got crazy unicode");
- }
- [currentData setValue:@"♖♘♗♕♔♗♘♖"];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error == nil, @"Should not abort");
- XCTAssertTrue(committed, @"Should commit");
- ready = YES;
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- }
- - (void)testImmediatelyAbortedTransactions {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- return [FIRTransactionResult abort];
- }];
- __block BOOL ready = NO;
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- return [FIRTransactionResult abort];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error == nil, @"No error occurred, we just aborted");
- XCTAssertFalse(committed, @"Should not commit");
- ready = YES;
- }];
- [self waitUntil:^BOOL {
- return ready;
- }];
- }
- - (void)testAddingToAnArrayWithATransaction {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- [ref setValue:@[ @"cat", @"horse" ]
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- done = NO;
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- if (val != [NSNull null]) {
- NSArray *arr = val;
- NSMutableArray *toSet = [arr mutableCopy];
- [toSet addObject:@"dog"];
- [currentData setValue:toSet];
- return [FIRTransactionResult successWithValue:currentData];
- } else {
- [currentData setValue:@[ @"dog" ]];
- return [FIRTransactionResult successWithValue:currentData];
- }
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should commit");
- NSArray *val = [snapshot value];
- NSArray *expected = @[ @"cat", @"horse", @"dog" ];
- XCTAssertTrue([val isEqualToArray:expected], @"Got whole array");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- - (void)testMergedTransactionsHaveCorrectSnapshotInOnComplete {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *node1 = refs.one;
- FIRDatabaseReference *node2 = refs.two;
- __block BOOL done = NO;
- [node1 setValue:@{@"a" : @0}
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- __block BOOL transaction1Done = NO;
- __block BOOL transaction2Done = NO;
- [node2
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- if (val != [NSNull null]) {
- XCTAssertTrue([@{@"a" : @0} isEqualToDictionary:val], @"Got initial data");
- }
- [currentData setValue:@{@"a" : @1}];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should commit");
- XCTAssertTrue([snapshot.key isEqualToString:node2.key], @"Correct snapshot name");
- NSDictionary *val = [snapshot value];
- // Per new behavior, will include the accepted value of the transaction, if it was
- // successful.
- NSDictionary *expected = @{@"a" : @1};
- XCTAssertTrue([val isEqualToDictionary:expected], @"Got final result");
- transaction1Done = YES;
- }];
- [[node2 child:@"a"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id val = [currentData value];
- if (val != [NSNull null]) {
- XCTAssertTrue([@1 isEqualToNumber:val], @"Got initial data");
- }
- [currentData setValue:@2];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(committed, @"Should commit");
- XCTAssertTrue([snapshot.key isEqualToString:@"a"], @"Correct snapshot name");
- NSNumber *val = [snapshot value];
- NSNumber *expected = @2;
- XCTAssertTrue([val isEqualToNumber:expected], @"Got final result");
- transaction2Done = YES;
- }];
- [self waitUntil:^BOOL {
- return transaction1Done && transaction2Done;
- }];
- }
- // Skipping two tests on nested calls. Since iOS uses a work queue, nested calls don't actually
- // happen synchronously, so they aren't problematic
- - (void)testPendingTransactionsAreCancelledOnDisconnect {
- FIRDatabaseConfig *cfg = [FTestHelpers configForName:@"pending-transactions"];
- FIRDatabaseReference *ref = [[[FTestHelpers databaseForConfig:cfg] reference] childByAutoId];
- __block BOOL done = NO;
- [[ref child:@"a"] setValue:@"initial"
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- done = NO;
- [[ref child:@"b"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@"new"];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertFalse(committed, @"Should not commit");
- XCTAssertTrue(error != nil, @"Should be an error");
- done = YES;
- }];
- [FRepoManager interrupt:cfg];
- [self waitUntil:^BOOL {
- return done;
- }];
- // cleanup
- [FRepoManager interrupt:cfg];
- [FRepoManager disposeRepos:cfg];
- }
- - (void)testTransactionWithoutLocalEvents1 {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- NSMutableArray *values = [[NSMutableArray alloc] init];
- [ref observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- [values addObject:[snapshot value]];
- }];
- [self waitUntil:^BOOL {
- // get initial data
- return values.count > 0;
- }];
- __block BOOL done = NO;
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@"hello!"];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error == nil, @"Should not be an error");
- XCTAssertTrue(committed, @"Committed");
- XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"got correct snapshot");
- done = YES;
- }
- withLocalEvents:NO];
- NSArray *expected = @[ [NSNull null] ];
- XCTAssertTrue([values isEqualToArray:expected], @"Should not have gotten any values yet");
- [self waitUntil:^BOOL {
- return done;
- }];
- expected = @[ [NSNull null], @"hello!" ];
- XCTAssertTrue([values isEqualToArray:expected], @"Should have the new value now");
- }
- - (void)testTransactionWithoutLocalEvents2 {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *ref1 = refs.one;
- FIRDatabaseReference *ref2 = refs.two;
- int SETS = 4;
- [ref1.repo setHijackHash:YES];
- NSMutableArray *events = [[NSMutableArray alloc] init];
- [ref1 setValue:@0];
- [ref1 observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- [events addObject:[snapshot value]];
- }];
- [self waitUntil:^BOOL {
- return events.count > 0;
- }];
- NSArray *expected = @[ @0 ];
- XCTAssertTrue([events isEqualToArray:expected], @"Got initial set");
- __block int retries = 0;
- __block BOOL done = NO;
- [ref1
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- retries++;
- id val = [currentData value];
- NSNumber *num = @0;
- if (val != [NSNull null]) {
- num = val;
- }
- int eventCount = [num intValue];
- if (eventCount == SETS - 1) {
- [ref1.repo setHijackHash:NO];
- }
- [currentData setValue:@"txn result"];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertTrue(error == nil, @"Should not be an error");
- XCTAssertTrue(committed, @"Committed");
- XCTAssertTrue([[snapshot value] isEqualToString:@"txn result"], @"got correct snapshot");
- done = YES;
- }
- withLocalEvents:NO];
- // Meanwhile, do sets from the second connection
- for (int i = 0; i < SETS; ++i) {
- __block BOOL setDone = NO;
- [ref2 setValue:[NSNumber numberWithInt:i]
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- setDone = YES;
- }];
- [self waitUntil:^BOOL {
- return setDone;
- }];
- }
- [self waitUntil:^BOOL {
- return done;
- }];
- XCTAssertTrue(retries > 0, @"Transaction should have retried");
- XCTAssertEqualObjects([events lastObject], @"txn result",
- @"Final value matches expected value from txn");
- }
- // Skipping test of calling transaction from value callback. Since all api calls are async on iOS,
- // nested calls are not a problem.
- - (void)testTransactionRevertsDataWhenAddADeeperListen {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *ref1 = refs.one;
- FIRDatabaseReference *ref2 = refs.two;
- __block BOOL done = NO;
- [[ref1 child:@"y"] setValue:@"test"
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- [ref2 runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- if (currentData.value == [NSNull null]) {
- [[currentData childDataByAppendingPath:@"x"] setValue:@5];
- return [FIRTransactionResult successWithValue:currentData];
- } else {
- return [FIRTransactionResult abort];
- }
- }];
- [[ref2 child:@"y"] observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- if ([snapshot.value isEqual:@"test"]) {
- done = YES;
- }
- }];
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- - (void)testTransactionWithIntegerKeys {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- NSDictionary *toSet = @{@"1" : @1, @"5" : @5, @"10" : @10, @"20" : @20};
- [ref setValue:toSet
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@42];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"Error should be nil.");
- XCTAssertTrue(committed, @"Transaction should have committed.");
- done = YES;
- }];
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- // https://app.asana.com/0/5673976843758/9259161251948
- - (void)testBubbleAppTransactionBug {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- [[ref child:@"a"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@1];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
- }];
- [[ref child:@"a"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- NSNumber *val = currentData.value;
- NSNumber *new = [ NSNumber numberWithInt : (val.intValue + 42) ];
- [currentData setValue:new];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
- }];
- [[ref child:@"b"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@7];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
- }];
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- NSNumber *a = [currentData childDataByAppendingPath:@"a"].value;
- NSNumber *b = [currentData childDataByAppendingPath:@"b"].value;
- NSNumber *new = [ NSNumber numberWithInt : a.intValue + b.intValue ];
- [currentData setValue:new];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"Error should be nil.");
- XCTAssertTrue(committed, @"Committed should be true.");
- XCTAssertEqualObjects(@50, snapshot.value, @"Result should be 50.");
- done = YES;
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- // If we have cached data, transactions shouldn't run on null.
- - (void)testTransactionsAreRunInitiallyOnCurrentlyCachedData {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- id initialData = @{@"a" : @"a-val", @"b" : @"b-val"};
- __block BOOL done = NO;
- __weak FIRDatabaseReference *weakRef = ref;
- [ref setValue:initialData
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *r) {
- [weakRef observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- [weakRef runTransactionBlock:^FIRTransactionResult *(
- FIRMutableData *currentData) {
- XCTAssertEqualObjects(currentData.value, initialData,
- @"Should be initial data.");
- done = YES;
- return [FIRTransactionResult abort];
- }];
- }];
- }];
- [self waitUntil:^BOOL {
- return done;
- }];
- }
- - (void)testMultipleLevels {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- return [FIRTransactionResult successWithValue:currentData];
- }];
- [[ref child:@"a"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- return [FIRTransactionResult successWithValue:currentData];
- }];
- [[ref child:@"b"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- return [FIRTransactionResult successWithValue:currentData];
- }];
- [ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- done = YES;
- }];
- WAIT_FOR(done);
- }
- - (void)testLocalServerValuesEventuallyButNotImmediatelyMatchServerWithTxns {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *writer = refs.one;
- FIRDatabaseReference *reader = refs.two;
- __block int done = 0;
- NSMutableArray *readSnaps = [[NSMutableArray alloc] init];
- NSMutableArray *writeSnaps = [[NSMutableArray alloc] init];
- [reader observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- if ([snapshot value] != [NSNull null]) {
- [readSnaps addObject:snapshot];
- if (readSnaps.count == 1) {
- done += 1;
- }
- }
- }];
- [writer observeEventType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- if ([snapshot value] != [NSNull null]) {
- [writeSnaps addObject:snapshot];
- if (writeSnaps.count == 2) {
- done += 1;
- }
- }
- }];
- [writer
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:[FIRServerValue timestamp]];
- [currentData setPriority:[FIRServerValue timestamp]];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
- }];
- [self waitUntil:^BOOL {
- return done == 2;
- }];
- XCTAssertEqual((unsigned long)[readSnaps count], (unsigned long)1,
- @"Should have received one snapshot on reader");
- XCTAssertEqual((unsigned long)[writeSnaps count], (unsigned long)2,
- @"Should have received two snapshots on writer");
- FIRDataSnapshot *firstReadSnap = [readSnaps objectAtIndex:0];
- FIRDataSnapshot *firstWriteSnap = [writeSnaps objectAtIndex:0];
- FIRDataSnapshot *secondWriteSnap = [writeSnaps objectAtIndex:1];
- NSNumber *now = [NSNumber numberWithDouble:round([[NSDate date] timeIntervalSince1970] * 1000)];
- XCTAssertTrue([now doubleValue] - [firstWriteSnap.value doubleValue] < 2000,
- @"Should have received a local event with a value close to timestamp");
- XCTAssertTrue([now doubleValue] - [firstWriteSnap.priority doubleValue] < 2000,
- @"Should have received a local event with a priority close to timestamp");
- XCTAssertTrue([now doubleValue] - [secondWriteSnap.value doubleValue] < 2000,
- @"Should have received a server event with a value close to timestamp");
- XCTAssertTrue([now doubleValue] - [secondWriteSnap.priority doubleValue] < 2000,
- @"Should have received a server event with a priority close to timestamp");
- XCTAssertFalse([firstWriteSnap value] == [secondWriteSnap value],
- @"Initial and future writer values should be different");
- XCTAssertFalse([firstWriteSnap priority] == [secondWriteSnap priority],
- @"Initial and future writer priorities should be different");
- XCTAssertEqualObjects(firstReadSnap.value, secondWriteSnap.value,
- @"Eventual reader and writer values should be equal");
- XCTAssertEqualObjects(firstReadSnap.priority, secondWriteSnap.priority,
- @"Eventual reader and writer priorities should be equal");
- }
- - (void)testTransactionWithQueryListen {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL done = NO;
- [ref setValue:@{@"a" : @1, @"b" : @2}
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- [[ref queryLimitedToFirst:1]
- observeEventType:FIRDataEventTypeChildAdded
- andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
- }
- withCancelBlock:^(NSError *error){
- }];
- [[ref child:@"a"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"This transaction should not have an error");
- XCTAssertTrue(committed, @"Should not have aborted");
- XCTAssertEqualObjects([snapshot value], @1,
- @"Transaction value should match initial set");
- done = YES;
- }];
- }];
- WAIT_FOR(done);
- }
- - (void)testTransactionDoesNotPickUpCachedDataFromPreviousOnce {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *me = refs.one;
- FIRDatabaseReference *other = refs.two;
- __block BOOL done = NO;
- [me setValue:@"not null"
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- done = YES;
- }];
- WAIT_FOR(done);
- done = NO;
- [me observeSingleEventOfType:FIRDataEventTypeValue
- withBlock:^(FIRDataSnapshot *snapshot) {
- done = YES;
- }];
- WAIT_FOR(done);
- done = NO;
- [other setValue:[NSNull null]
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- done = YES;
- }];
- WAIT_FOR(done);
- done = NO;
- [me
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id current = [currentData value];
- if (current == [NSNull null]) {
- [currentData setValue:@"it was null!"];
- } else {
- [currentData setValue:@"it was not null!"];
- }
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"This transaction should not have an error");
- XCTAssertTrue(committed, @"Should not have aborted");
- XCTAssertEqualObjects([snapshot value], @"it was null!",
- @"Transaction value should match remote null set");
- done = YES;
- }];
- WAIT_FOR(done);
- }
- - (void)testTransactionDoesNotPickUpCachedDataFromPreviousTransaction {
- FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
- FIRDatabaseReference *me = refs.one;
- FIRDatabaseReference *other = refs.two;
- __block BOOL done = NO;
- [me
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@"not null"];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"This transaction should not have an error");
- XCTAssertTrue(committed, @"Should not have aborted");
- done = YES;
- }];
- WAIT_FOR(done);
- done = NO;
- [other setValue:[NSNull null]
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- done = YES;
- }];
- WAIT_FOR(done);
- done = NO;
- [me
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id current = [currentData value];
- if (current == [NSNull null]) {
- [currentData setValue:@"it was null!"];
- } else {
- [currentData setValue:@"it was not null!"];
- }
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"This transaction should not have an error");
- XCTAssertTrue(committed, @"Should not have aborted");
- XCTAssertEqualObjects([snapshot value], @"it was null!",
- @"Transaction value should match remote null set");
- done = YES;
- }];
- WAIT_FOR(done);
- }
- - (void)testTransactionOnQueriedLocationDoesntRunInitiallyOnNull {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL txnDone = NO;
- [self waitForCompletionOf:[ref childByAutoId] setValue:@{@"a" : @1, @"b" : @2}];
- [[ref queryLimitedToFirst:1]
- observeEventType:FIRDataEventTypeChildAdded
- withBlock:^(FIRDataSnapshot *snapshot) {
- [snapshot.ref
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- id expected = @{@"a" : @1, @"b" : @2};
- XCTAssertEqualObjects(currentData.value, expected, @"");
- [currentData setValue:[NSNull null]];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"");
- XCTAssertTrue(committed, @"");
- XCTAssertEqualObjects(snapshot.value, [NSNull null], @"");
- txnDone = YES;
- }];
- }];
- WAIT_FOR(txnDone);
- }
- - (void)testTransactionsRaiseCorrectChildChangedEventsOnQueries {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL txnDone = NO;
- NSMutableArray *snapshots = [[NSMutableArray alloc] init];
- [self waitForCompletionOf:ref setValue:@{@"foo" : @{@"value" : @1}}];
- FIRDatabaseQuery *query = [ref queryEndingAtValue:@(DBL_MIN)];
- [query observeEventType:FIRDataEventTypeChildAdded
- withBlock:^(FIRDataSnapshot *snapshot) {
- [snapshots addObject:snapshot];
- }];
- [query observeEventType:FIRDataEventTypeChildChanged
- withBlock:^(FIRDataSnapshot *snapshot) {
- [snapshots addObject:snapshot];
- }];
- [[ref child:@"foo"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [[currentData childDataByAppendingPath:@"value"] setValue:@2];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"");
- XCTAssertTrue(committed, @"");
- txnDone = YES;
- }
- withLocalEvents:NO];
- WAIT_FOR(txnDone);
- XCTAssertTrue(snapshots.count == 2, @"");
- FIRDataSnapshot *addedSnapshot = snapshots[0];
- XCTAssertEqualObjects(addedSnapshot.key, @"foo", @"");
- XCTAssertEqualObjects(addedSnapshot.value, @{@"value" : @1}, @"");
- FIRDataSnapshot *changedSnapshot = snapshots[1];
- XCTAssertEqualObjects(changedSnapshot.key, @"foo", @"");
- XCTAssertEqualObjects(changedSnapshot.value, @{@"value" : @2}, @"");
- }
- - (void)testTransactionsUseLocalMerges {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- __block BOOL txnDone = NO;
- [ref updateChildValues:@{@"foo" : @"bar"}];
- [[ref child:@"foo"]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- XCTAssertEqualObjects(currentData.value, @"bar",
- @"Transaction value matches local updates");
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error, @"");
- XCTAssertTrue(committed, @"");
- txnDone = YES;
- }];
- WAIT_FOR(txnDone);
- }
- // See https://app.asana.com/0/15566422264127/23303789496881
- - (void)testOutOfOrderRemoveWritesAreHandledCorrectly {
- FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
- [ref setValue:@{@"foo" : @"bar"}];
- [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@"transaction-1"];
- return [FIRTransactionResult successWithValue:currentData];
- }];
- [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@"transaction-2"];
- return [FIRTransactionResult successWithValue:currentData];
- }];
- __block BOOL done = NO;
- // This will trigger an abort of the transaction which should not cause the client to crash
- [ref updateChildValues:@{@"qux" : @"quu"}
- withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
- XCTAssertNil(error);
- done = YES;
- }];
- WAIT_FOR(done);
- }
- - (void)testUnsentTransactionsAreNotCancelledOnDisconnect {
- // Hack: To trigger us to disconnect before restoring state, we inject a bad auth token.
- // In real-world usage the much more common case is that we get redirected to a different
- // server, but that's harder to manufacture from a test.
- NSString *configName = @"testUnsentTransactionsAreNotCancelledOnDisconnect";
- FIRDatabaseConfig *config = [FTestHelpers configForName:configName];
- config.contextProvider = [[FIROneBadTokenProvider alloc] init];
- // Queue a transaction offline.
- FIRDatabaseReference *root = [[FTestHelpers databaseForConfig:config] reference];
- [root.database goOffline];
- __block BOOL done = NO;
- [[root childByAutoId]
- runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
- [currentData setValue:@0];
- return [FIRTransactionResult successWithValue:currentData];
- }
- andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
- XCTAssertNil(error);
- XCTAssertTrue(committed);
- done = YES;
- }];
- [root.database goOnline];
- WAIT_FOR(done);
- }
- @end
|