FTransactionTest.m 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FirebaseDatabase/Tests/Integration/FTransactionTest.h"
  17. #import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h"
  18. #import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h"
  19. #import "FirebaseDatabase/Tests/Helpers/FEventTester.h"
  20. #import "FirebaseDatabase/Tests/Helpers/FTestHelpers.h"
  21. #import "FirebaseDatabase/Tests/Helpers/FTupleEventTypeString.h"
  22. // HACK used by testUnsentTransactionsAreNotCancelledOnDisconnect to return one bad token and then a
  23. // nil token.
  24. @interface FIROneBadTokenProvider : NSObject <FIRDatabaseConnectionContextProvider> {
  25. BOOL firstFetch;
  26. }
  27. @end
  28. @implementation FIROneBadTokenProvider
  29. - (instancetype)init {
  30. self = [super init];
  31. if (self) {
  32. firstFetch = YES;
  33. }
  34. return self;
  35. }
  36. - (void)fetchContextForcingRefresh:(BOOL)forceRefresh
  37. withCallback:(void (^)(FIRDatabaseConnectionContext *_Nullable context,
  38. NSError *_Nullable error))callback {
  39. // Simulate delay
  40. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_MSEC)),
  41. [FIRDatabaseQuery sharedQueue], ^{
  42. if (self->firstFetch) {
  43. self->firstFetch = NO;
  44. __auto_type context =
  45. [[FIRDatabaseConnectionContext alloc] initWithAuthToken:@"bad-token"
  46. appCheckToken:nil];
  47. callback(context, nil);
  48. } else {
  49. callback(nil, nil);
  50. }
  51. });
  52. }
  53. - (void)listenForAuthTokenChanges:(fbt_void_nsstring)listener {
  54. }
  55. - (void)listenForAppCheckTokenChanges:(nonnull fbt_void_nsstring)listener {
  56. }
  57. @end
  58. @implementation FTransactionTest
  59. - (void)testNewValueIsImmediatelyVisible {
  60. FIRDatabaseReference *node = [FTestHelpers getRandomNode];
  61. __block BOOL runOnce = NO;
  62. [[node child:@"foo"] runTransactionBlock:^(FIRMutableData *currentValue) {
  63. runOnce = YES;
  64. [currentValue setValue:@42];
  65. return [FIRTransactionResult successWithValue:currentValue];
  66. }];
  67. [self waitUntil:^BOOL {
  68. return runOnce;
  69. }];
  70. __block BOOL ready = NO;
  71. [[node child:@"foo"]
  72. observeEventType:FIRDataEventTypeValue
  73. withBlock:^(FIRDataSnapshot *snapshot) {
  74. if (!ready) {
  75. NSNumber *val = [snapshot value];
  76. XCTAssertTrue([val isEqualToNumber:@42], @"Got value set in transaction");
  77. ready = YES;
  78. }
  79. }];
  80. [self waitUntil:^BOOL {
  81. return ready;
  82. }];
  83. }
  84. - (void)testNonAbortedTransactionSetsCommittedToTrueInCallback {
  85. FIRDatabaseReference *node = [FTestHelpers getRandomNode];
  86. __block BOOL done = NO;
  87. [[node child:@"foo"]
  88. runTransactionBlock:^(FIRMutableData *currentValue) {
  89. [currentValue setValue:@42];
  90. return [FIRTransactionResult successWithValue:currentValue];
  91. }
  92. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  93. XCTAssertTrue(committed, @"Should not have aborted");
  94. done = YES;
  95. }];
  96. [self waitUntil:^BOOL {
  97. return done;
  98. }];
  99. }
  100. - (void)testAbortedTransactionSetsCommittedToFalseInCallback {
  101. FIRDatabaseReference *node = [FTestHelpers getRandomNode];
  102. __block BOOL done = NO;
  103. [[node child:@"foo"]
  104. runTransactionBlock:^(FIRMutableData *currentValue) {
  105. return [FIRTransactionResult abort];
  106. }
  107. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  108. XCTAssertFalse(committed, @"Should have aborted");
  109. done = YES;
  110. }];
  111. [self waitUntil:^BOOL {
  112. return done;
  113. }];
  114. }
  115. - (void)testBugTestSetDataReconnectDoTransactionThatAbortsOnceDataArrivesVerifyCorrectEvents {
  116. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  117. FIRDatabaseReference *reader = refs.one;
  118. __block BOOL dataWritten = NO;
  119. [[reader child:@"foo"] setValue:@42
  120. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  121. dataWritten = YES;
  122. }];
  123. [self waitUntil:^BOOL {
  124. return dataWritten;
  125. }];
  126. FIRDatabaseReference *writer = refs.two;
  127. __block int eventsReceived = 0;
  128. [[writer child:@"foo"]
  129. observeEventType:FIRDataEventTypeValue
  130. withBlock:^(FIRDataSnapshot *snapshot) {
  131. if (eventsReceived == 0) {
  132. NSString *val = [snapshot value];
  133. XCTAssertTrue([val isEqualToString:@"temp value"],
  134. @"Got initial transaction value");
  135. } else if (eventsReceived == 1) {
  136. NSNumber *val = [snapshot value];
  137. XCTAssertTrue([val isEqualToNumber:@42], @"Got hidden original value");
  138. } else {
  139. XCTFail(@"Too many events");
  140. }
  141. eventsReceived++;
  142. }];
  143. [[writer child:@"foo"]
  144. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  145. id current = [currentData value];
  146. if (current == [NSNull null]) {
  147. [currentData setValue:@"temp value"];
  148. return [FIRTransactionResult successWithValue:currentData];
  149. } else {
  150. return [FIRTransactionResult abort];
  151. }
  152. }
  153. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  154. XCTAssertFalse(committed, @"This transaction should never commit");
  155. XCTAssertTrue(error == nil, @"This transaction should not have an error");
  156. }];
  157. [self waitUntil:^BOOL {
  158. return eventsReceived == 2;
  159. }];
  160. }
  161. - (void)testUseTransactionToCreateANodeMakeSureExactlyOneEventIsReceived {
  162. FIRDatabaseReference *node = [FTestHelpers getRandomNode];
  163. __block int events = 0;
  164. __block BOOL done = NO;
  165. [[node child:@"a"] observeEventType:FIRDataEventTypeValue
  166. withBlock:^(FIRDataSnapshot *snapshot) {
  167. events++;
  168. if (events > 1) {
  169. XCTFail(@"Too many events");
  170. }
  171. }];
  172. [[node child:@"a"]
  173. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  174. [currentData setValue:@42];
  175. return [FIRTransactionResult successWithValue:currentData];
  176. }
  177. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  178. done = YES;
  179. }];
  180. [self waitUntil:^BOOL {
  181. return done && events == 1;
  182. }];
  183. }
  184. - (void)testUseTransactionToUpdateTwoExistingChildNodesMakeSureEventsAreOnlyRaisedForChangedNode {
  185. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  186. FIRDatabaseReference *node1 = [refs.one child:@"foo"];
  187. FIRDatabaseReference *node2 = [refs.two child:@"foo"];
  188. __block BOOL ready = NO;
  189. [[node1 child:@"a"] setValue:@42];
  190. [[node1 child:@"b"] setValue:@42
  191. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  192. ready = YES;
  193. }];
  194. [self waitUntil:^BOOL {
  195. return ready;
  196. }];
  197. FEventTester *et = [[FEventTester alloc] initFrom:self];
  198. NSArray *expect = @[
  199. [[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"a"]
  200. withEvent:FIRDataEventTypeValue
  201. withString:nil],
  202. [[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"b"]
  203. withEvent:FIRDataEventTypeValue
  204. withString:nil]
  205. ];
  206. [et addLookingFor:expect];
  207. [et wait];
  208. expect = @[ [[FTupleEventTypeString alloc] initWithFirebase:[node2 child:@"b"]
  209. withEvent:FIRDataEventTypeValue
  210. withString:nil] ];
  211. [et addLookingFor:expect];
  212. ready = NO;
  213. [node2
  214. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  215. NSDictionary *toSet = @{@"a" : @42, @"b" : @87};
  216. [currentData setValue:toSet];
  217. return [FIRTransactionResult successWithValue:currentData];
  218. }
  219. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  220. ready = YES;
  221. }];
  222. [self waitUntil:^BOOL {
  223. return ready;
  224. }];
  225. [et wait];
  226. }
  227. - (void)testTransactionOnlyCalledOnceWhenInitializingAnEmptyNode {
  228. FIRDatabaseReference *node = [FTestHelpers getRandomNode];
  229. __block BOOL updateCalled = NO;
  230. [node runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  231. id val = [currentData value];
  232. XCTAssertTrue(val == [NSNull null], @"Should be no value here to start with");
  233. if (updateCalled) {
  234. XCTFail(@"Should not be called again");
  235. }
  236. updateCalled = YES;
  237. [currentData setValue:@{@"a" : @5, @"b" : @6}];
  238. return [FIRTransactionResult successWithValue:currentData];
  239. }];
  240. [self waitUntil:^BOOL {
  241. return updateCalled;
  242. }];
  243. }
  244. - (void)testSecondTransactionGetsRunImmediatelyOnPreviousOutputAndOnlyRunsOnce {
  245. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  246. FIRDatabaseReference *ref1 = refs.one;
  247. FIRDatabaseReference *ref2 = refs.two;
  248. __block BOOL firstRun = NO;
  249. __block BOOL firstDone = NO;
  250. __block BOOL secondRun = NO;
  251. __block BOOL secondDone = NO;
  252. [ref1
  253. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  254. XCTAssertFalse(firstRun, @"Should not be run twice");
  255. firstRun = YES;
  256. [currentData setValue:@42];
  257. return [FIRTransactionResult successWithValue:currentData];
  258. }
  259. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  260. XCTAssertTrue(committed, @"Should not fail");
  261. firstDone = YES;
  262. }];
  263. [self waitUntil:^BOOL {
  264. return firstRun;
  265. }];
  266. [ref1
  267. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  268. XCTAssertFalse(secondRun, @"Should only run once");
  269. secondRun = YES;
  270. NSNumber *val = [currentData value];
  271. XCTAssertTrue([val isEqualToNumber:@42], @"Should see result of last transaction");
  272. [currentData setValue:@84];
  273. return [FIRTransactionResult successWithValue:currentData];
  274. }
  275. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  276. XCTAssertTrue(committed, @"Should not fail");
  277. secondDone = YES;
  278. }];
  279. [self waitUntil:^BOOL {
  280. return secondRun;
  281. }];
  282. __block FIRDataSnapshot *snap = nil;
  283. [ref1 observeSingleEventOfType:FIRDataEventTypeValue
  284. withBlock:^(FIRDataSnapshot *snapshot) {
  285. snap = snapshot;
  286. }];
  287. [self waitUntil:^BOOL {
  288. return snap != nil;
  289. }];
  290. XCTAssertTrue([[snap value] isEqualToNumber:@84], @"Should get updated value");
  291. [self waitUntil:^BOOL {
  292. return firstDone && secondDone;
  293. }];
  294. snap = nil;
  295. [ref2 observeSingleEventOfType:FIRDataEventTypeValue
  296. withBlock:^(FIRDataSnapshot *snapshot) {
  297. snap = snapshot;
  298. }];
  299. [self waitUntil:^BOOL {
  300. return snap != nil;
  301. }];
  302. XCTAssertTrue([[snap value] isEqualToNumber:@84], @"Should get updated value");
  303. }
  304. // The js test, "Set() cancels pending transactions and re-runs affected transactions.", does not
  305. // cleanly port to ios due to everything being asynchronous. Rather than attempt to mitigate the
  306. // various race conditions inherent in a port, I'm adding tests to cover the specific behaviors
  307. // wrapped up in that one test.
  308. - (void)testSetCancelsPendingTransaction {
  309. FIRDatabaseReference *node = [FTestHelpers getRandomNode];
  310. __block FIRDataSnapshot *nodeSnap = nil;
  311. __block FIRDataSnapshot *nodeFooSnap = nil;
  312. [node observeEventType:FIRDataEventTypeValue
  313. withBlock:^(FIRDataSnapshot *snapshot) {
  314. nodeSnap = snapshot;
  315. }];
  316. [[node child:@"foo"] observeEventType:FIRDataEventTypeValue
  317. withBlock:^(FIRDataSnapshot *snapshot) {
  318. nodeFooSnap = snapshot;
  319. }];
  320. __block BOOL firstDone = NO;
  321. __block BOOL secondDone = NO;
  322. __block BOOL firstRun = NO;
  323. [[node child:@"foo"]
  324. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  325. XCTAssertFalse(firstRun, @"Should only run once");
  326. firstRun = YES;
  327. [currentData setValue:@42];
  328. return [FIRTransactionResult successWithValue:currentData];
  329. }
  330. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  331. XCTAssertTrue(committed, @"Should not fail");
  332. firstDone = YES;
  333. }];
  334. [self waitUntil:^BOOL {
  335. return nodeFooSnap != nil;
  336. }];
  337. XCTAssertTrue([[nodeFooSnap value] isEqualToNumber:@42], @"Got first value");
  338. [node
  339. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  340. [currentData setValue:@{@"foo" : @84, @"bar" : @1}];
  341. return [FIRTransactionResult successWithValue:currentData];
  342. }
  343. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  344. XCTAssertFalse(committed, @"This should not ever be committed");
  345. secondDone = YES;
  346. }];
  347. [self waitUntil:^BOOL {
  348. return nodeSnap != nil;
  349. }];
  350. [[node child:@"foo"] setValue:@0];
  351. }
  352. // It's difficult to force a transaction re-run on ios, since everything is async. There is also an
  353. // outstanding case that prevents this test from being before a connection is established (#1981)
  354. /*
  355. - (void) testSetRerunsAffectedTransactions {
  356. Firebase* node = [FTestHelpers getRandomNode];
  357. __block BOOL ready = NO;
  358. [[node.parent child:@".info/connected"] observeEventType:FIRDataEventTypeValue
  359. withBlock:^(FIRDataSnapshot *snapshot) { ready = [[snapshot value] boolValue];
  360. }];
  361. [self waitUntil:^BOOL{
  362. return ready;
  363. }];
  364. __block FIRDataSnapshot* nodeSnap = nil;
  365. [node observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  366. nodeSnap = snapshot;
  367. NSLog(@"SNAP value: %@", [snapshot value]);
  368. }];
  369. __block BOOL firstDone = NO;
  370. __block BOOL secondDone = NO;
  371. __block BOOL firstRun = NO;
  372. __block int secondCount = 0;
  373. __block BOOL setDone = NO;
  374. [node runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  375. STAssertFalse(firstRun, @"Should only run once");
  376. firstRun = YES;
  377. [currentData setValue:@42];
  378. return [FIRTransactionResult successWithValue:currentData];
  379. } andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  380. STAssertTrue(committed, @"Should not fail");
  381. firstDone = YES;
  382. }];
  383. [[node child:@"bar"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  384. NSLog(@"RUNNING TRANSACTION");
  385. secondCount++;
  386. id val = [currentData value];
  387. if (secondCount == 1) {
  388. STAssertTrue(val == [NSNull null], @"Should not have a value");
  389. [currentData setValue:@"first"];
  390. return [FIRTransactionResult successWithValue:currentData];
  391. } else if (secondCount == 2) {
  392. NSLog(@"val: %@", val);
  393. STAssertTrue(val == [NSNull null], @"Should not have a value");
  394. [currentData setValue:@"second"];
  395. return [FIRTransactionResult successWithValue:currentData];
  396. } else {
  397. STFail(@"Called too many times");
  398. return [FIRTransactionResult abort];
  399. }
  400. } andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  401. STAssertTrue(committed, @"Should eventually be committed");
  402. secondDone = YES;
  403. }];
  404. [[node child:@"foo"] setValue:@0 andCompletionBlock:^(NSError *error) {
  405. setDone = YES;
  406. }];
  407. [self waitUntil:^BOOL{
  408. return setDone;
  409. }];
  410. NSDictionary* expected = @{@"bar": @"second", @"foo": @0};
  411. STAssertTrue([[nodeSnap value] isEqualToDictionary:expected], @"Got last value");
  412. STAssertTrue(secondCount == 2, @"Should have re-run second transaction");
  413. }*/
  414. - (void)testTransactionSetSetWorks {
  415. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  416. __block BOOL done = NO;
  417. [ref
  418. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  419. id val = [currentData value];
  420. XCTAssertTrue(val == [NSNull null], @"Initial data should be null");
  421. [currentData setValue:@"hi!"];
  422. return [FIRTransactionResult successWithValue:currentData];
  423. }
  424. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  425. XCTAssertTrue(error == nil, @"Should not be an error");
  426. XCTAssertTrue(committed, @"Should commit");
  427. done = YES;
  428. }];
  429. [ref setValue:@"foo"];
  430. [ref setValue:@"bar"];
  431. [self waitUntil:^BOOL {
  432. return done;
  433. }];
  434. }
  435. - (void)testPriorityIsNotPreservedWhenSettingData {
  436. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  437. __block FIRDataSnapshot *snap = nil;
  438. [ref observeEventType:FIRDataEventTypeValue
  439. withBlock:^(FIRDataSnapshot *snapshot) {
  440. snap = snapshot;
  441. }];
  442. [ref setValue:@"test" andPriority:@5];
  443. __block BOOL ready = NO;
  444. [ref
  445. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  446. [currentData setValue:@"new value"];
  447. return [FIRTransactionResult successWithValue:currentData];
  448. }
  449. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  450. ready = YES;
  451. }];
  452. [self waitUntil:^BOOL {
  453. return ready;
  454. }];
  455. id val = [snap value];
  456. id pri = [snap priority];
  457. XCTAssertTrue(pri == [NSNull null], @"Got priority");
  458. XCTAssertTrue([val isEqualToString:@"new value"], @"Get new value");
  459. }
  460. // Skipping test with nested transactions. Everything is async on ios, so new transactions just get
  461. // placed in a queue
  462. - (void)testResultSnapshotIsPassedToOnComplete {
  463. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  464. FIRDatabaseReference *ref1 = refs.one;
  465. FIRDatabaseReference *ref2 = refs.two;
  466. __block BOOL done = NO;
  467. [ref1
  468. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  469. id val = [currentData value];
  470. if (val == [NSNull null]) {
  471. [currentData setValue:@"hello!"];
  472. return [FIRTransactionResult successWithValue:currentData];
  473. } else {
  474. return [FIRTransactionResult abort];
  475. }
  476. }
  477. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  478. XCTAssertTrue(committed, @"Should commit");
  479. XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
  480. done = YES;
  481. }];
  482. [self waitUntil:^BOOL {
  483. return done;
  484. }];
  485. // do it again for the aborted case
  486. done = NO;
  487. [ref1
  488. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  489. id val = [currentData value];
  490. if (val == [NSNull null]) {
  491. [currentData setValue:@"hello!"];
  492. return [FIRTransactionResult successWithValue:currentData];
  493. } else {
  494. return [FIRTransactionResult abort];
  495. }
  496. }
  497. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  498. XCTAssertFalse(committed, @"Should not commit");
  499. XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
  500. done = YES;
  501. }];
  502. [self waitUntil:^BOOL {
  503. return done;
  504. }];
  505. // do it again on a fresh connection, for the aborted case
  506. done = NO;
  507. [ref2
  508. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  509. id val = [currentData value];
  510. if (val == [NSNull null]) {
  511. [currentData setValue:@"hello!"];
  512. return [FIRTransactionResult successWithValue:currentData];
  513. } else {
  514. return [FIRTransactionResult abort];
  515. }
  516. }
  517. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  518. XCTAssertFalse(committed, @"Should not commit");
  519. XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"Got correct snapshot");
  520. done = YES;
  521. }];
  522. [self waitUntil:^BOOL {
  523. return done;
  524. }];
  525. }
  526. - (void)testTransactionAbortsAfter25Retries {
  527. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  528. [ref.repo setHijackHash:YES];
  529. __block int tries = 0;
  530. __block BOOL done = NO;
  531. [ref
  532. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  533. XCTAssertTrue(tries < 25, @"Should not be more than 25 tries");
  534. tries++;
  535. return [FIRTransactionResult successWithValue:currentData];
  536. }
  537. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  538. XCTAssertTrue(error != nil, @"Should fail, too many retries");
  539. XCTAssertFalse(committed, @"Should not commit");
  540. done = YES;
  541. }];
  542. [self waitUntil:^BOOL {
  543. return done;
  544. }];
  545. [ref.repo setHijackHash:NO];
  546. }
  547. - (void)testSetShouldCancelSentTransactionsThatComeBackAsDatastale {
  548. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  549. FIRDatabaseReference *ref1 = refs.one;
  550. FIRDatabaseReference *ref2 = refs.two;
  551. __block BOOL ready = NO;
  552. [ref1 setValue:@5
  553. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  554. ready = YES;
  555. }];
  556. [self waitUntil:^BOOL {
  557. return ready;
  558. }];
  559. ready = NO;
  560. [ref2
  561. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  562. id val = [currentData value];
  563. XCTAssertTrue(val == [NSNull null], @"No current value");
  564. [currentData setValue:@72];
  565. return [FIRTransactionResult successWithValue:currentData];
  566. }
  567. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  568. XCTAssertTrue(error != nil, @"Should abort");
  569. XCTAssertFalse(committed, @"Should not commit");
  570. ready = YES;
  571. }];
  572. [ref2 setValue:@32];
  573. [self waitUntil:^BOOL {
  574. return ready;
  575. }];
  576. }
  577. - (void)testUpdateShouldNotCancelUnrelatedTransactions {
  578. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  579. __block BOOL fooTransactionDone = NO;
  580. __block BOOL barTransactionDone = NO;
  581. [self waitForCompletionOf:[ref child:@"foo"] setValue:@"oldValue"];
  582. [ref.repo setHijackHash:YES];
  583. // This transaction should get cancelled as we update "foo" later on.
  584. [[ref child:@"foo"]
  585. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  586. [currentData setValue:@72];
  587. return [FIRTransactionResult successWithValue:currentData];
  588. }
  589. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  590. XCTAssertTrue(error != nil, @"Should abort");
  591. XCTAssertFalse(committed, @"Should not commit");
  592. fooTransactionDone = YES;
  593. }];
  594. // This transaction should not get cancelled since we don't update "bar".
  595. [[ref child:@"bar"]
  596. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  597. [currentData setValue:@72];
  598. return [FIRTransactionResult successWithValue:currentData];
  599. }
  600. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  601. // Note: In rare cases, this might get aborted since failed transactions (forced by
  602. // setHijackHash) are only retried 25 times. If we hit this limit before we stop hijacking
  603. // the hash below, this test will flake.
  604. XCTAssertTrue(error == nil, @"Should not abort");
  605. XCTAssertTrue(committed, @"Should commit");
  606. barTransactionDone = YES;
  607. }];
  608. NSDictionary *updateData = @{
  609. @"foo" : @"newValue",
  610. @"boo" : @"newValue",
  611. @"doo/foo" : @"newValue",
  612. @"loo" : @{@"doo" : @{@"boo" : @"newValue"}}
  613. };
  614. [self waitForCompletionOf:ref updateChildValues:updateData];
  615. XCTAssertTrue(fooTransactionDone, "Should have gotten cancelled before the update");
  616. XCTAssertFalse(barTransactionDone, "Should run after the update");
  617. [ref.repo setHijackHash:NO];
  618. WAIT_FOR(barTransactionDone);
  619. }
  620. - (void)testTransactionOnWackyUnicode {
  621. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  622. FIRDatabaseReference *ref1 = refs.one;
  623. FIRDatabaseReference *ref2 = refs.two;
  624. __block BOOL ready = NO;
  625. [ref1 setValue:@"♜♞♝♛♚♝♞♜"
  626. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  627. ready = YES;
  628. }];
  629. [self waitUntil:^BOOL {
  630. return ready;
  631. }];
  632. ready = NO;
  633. [ref2
  634. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  635. id val = [currentData value];
  636. if (val != [NSNull null]) {
  637. XCTAssertTrue([val isEqualToString:@"♜♞♝♛♚♝♞♜"], @"Got crazy unicode");
  638. }
  639. [currentData setValue:@"♖♘♗♕♔♗♘♖"];
  640. return [FIRTransactionResult successWithValue:currentData];
  641. }
  642. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  643. XCTAssertTrue(error == nil, @"Should not abort");
  644. XCTAssertTrue(committed, @"Should commit");
  645. ready = YES;
  646. }];
  647. [self waitUntil:^BOOL {
  648. return ready;
  649. }];
  650. }
  651. - (void)testImmediatelyAbortedTransactions {
  652. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  653. [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  654. return [FIRTransactionResult abort];
  655. }];
  656. __block BOOL ready = NO;
  657. [ref
  658. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  659. return [FIRTransactionResult abort];
  660. }
  661. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  662. XCTAssertTrue(error == nil, @"No error occurred, we just aborted");
  663. XCTAssertFalse(committed, @"Should not commit");
  664. ready = YES;
  665. }];
  666. [self waitUntil:^BOOL {
  667. return ready;
  668. }];
  669. }
  670. - (void)testAddingToAnArrayWithATransaction {
  671. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  672. __block BOOL done = NO;
  673. [ref setValue:@[ @"cat", @"horse" ]
  674. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  675. done = YES;
  676. }];
  677. [self waitUntil:^BOOL {
  678. return done;
  679. }];
  680. done = NO;
  681. [ref
  682. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  683. id val = [currentData value];
  684. if (val != [NSNull null]) {
  685. NSArray *arr = val;
  686. NSMutableArray *toSet = [arr mutableCopy];
  687. [toSet addObject:@"dog"];
  688. [currentData setValue:toSet];
  689. return [FIRTransactionResult successWithValue:currentData];
  690. } else {
  691. [currentData setValue:@[ @"dog" ]];
  692. return [FIRTransactionResult successWithValue:currentData];
  693. }
  694. }
  695. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  696. XCTAssertTrue(committed, @"Should commit");
  697. NSArray *val = [snapshot value];
  698. NSArray *expected = @[ @"cat", @"horse", @"dog" ];
  699. XCTAssertTrue([val isEqualToArray:expected], @"Got whole array");
  700. done = YES;
  701. }];
  702. [self waitUntil:^BOOL {
  703. return done;
  704. }];
  705. }
  706. - (void)testMergedTransactionsHaveCorrectSnapshotInOnComplete {
  707. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  708. FIRDatabaseReference *node1 = refs.one;
  709. FIRDatabaseReference *node2 = refs.two;
  710. __block BOOL done = NO;
  711. [node1 setValue:@{@"a" : @0}
  712. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  713. done = YES;
  714. }];
  715. [self waitUntil:^BOOL {
  716. return done;
  717. }];
  718. __block BOOL transaction1Done = NO;
  719. __block BOOL transaction2Done = NO;
  720. [node2
  721. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  722. id val = [currentData value];
  723. if (val != [NSNull null]) {
  724. XCTAssertTrue([@{@"a" : @0} isEqualToDictionary:val], @"Got initial data");
  725. }
  726. [currentData setValue:@{@"a" : @1}];
  727. return [FIRTransactionResult successWithValue:currentData];
  728. }
  729. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  730. XCTAssertTrue(committed, @"Should commit");
  731. XCTAssertTrue([snapshot.key isEqualToString:node2.key], @"Correct snapshot name");
  732. NSDictionary *val = [snapshot value];
  733. // Per new behavior, will include the accepted value of the transaction, if it was
  734. // successful.
  735. NSDictionary *expected = @{@"a" : @1};
  736. XCTAssertTrue([val isEqualToDictionary:expected], @"Got final result");
  737. transaction1Done = YES;
  738. }];
  739. [[node2 child:@"a"]
  740. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  741. id val = [currentData value];
  742. if (val != [NSNull null]) {
  743. XCTAssertTrue([@1 isEqualToNumber:val], @"Got initial data");
  744. }
  745. [currentData setValue:@2];
  746. return [FIRTransactionResult successWithValue:currentData];
  747. }
  748. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  749. XCTAssertTrue(committed, @"Should commit");
  750. XCTAssertTrue([snapshot.key isEqualToString:@"a"], @"Correct snapshot name");
  751. NSNumber *val = [snapshot value];
  752. NSNumber *expected = @2;
  753. XCTAssertTrue([val isEqualToNumber:expected], @"Got final result");
  754. transaction2Done = YES;
  755. }];
  756. [self waitUntil:^BOOL {
  757. return transaction1Done && transaction2Done;
  758. }];
  759. }
  760. // Skipping two tests on nested calls. Since iOS uses a work queue, nested calls don't actually
  761. // happen synchronously, so they aren't problematic
  762. - (void)testPendingTransactionsAreCancelledOnDisconnect {
  763. FIRDatabaseConfig *cfg = [FTestHelpers configForName:@"pending-transactions"];
  764. FIRDatabaseReference *ref = [[[FTestHelpers databaseForConfig:cfg] reference] childByAutoId];
  765. __block BOOL done = NO;
  766. [[ref child:@"a"] setValue:@"initial"
  767. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  768. done = YES;
  769. }];
  770. [self waitUntil:^BOOL {
  771. return done;
  772. }];
  773. done = NO;
  774. [[ref child:@"b"]
  775. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  776. [currentData setValue:@"new"];
  777. return [FIRTransactionResult successWithValue:currentData];
  778. }
  779. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  780. XCTAssertFalse(committed, @"Should not commit");
  781. XCTAssertTrue(error != nil, @"Should be an error");
  782. done = YES;
  783. }];
  784. [FRepoManager interrupt:cfg];
  785. [self waitUntil:^BOOL {
  786. return done;
  787. }];
  788. // cleanup
  789. [FRepoManager interrupt:cfg];
  790. [FRepoManager disposeRepos:cfg];
  791. }
  792. - (void)testTransactionWithoutLocalEvents1 {
  793. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  794. NSMutableArray *values = [[NSMutableArray alloc] init];
  795. [ref observeEventType:FIRDataEventTypeValue
  796. withBlock:^(FIRDataSnapshot *snapshot) {
  797. [values addObject:[snapshot value]];
  798. }];
  799. [self waitUntil:^BOOL {
  800. // get initial data
  801. return values.count > 0;
  802. }];
  803. __block BOOL done = NO;
  804. [ref
  805. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  806. [currentData setValue:@"hello!"];
  807. return [FIRTransactionResult successWithValue:currentData];
  808. }
  809. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  810. XCTAssertTrue(error == nil, @"Should not be an error");
  811. XCTAssertTrue(committed, @"Committed");
  812. XCTAssertTrue([[snapshot value] isEqualToString:@"hello!"], @"got correct snapshot");
  813. done = YES;
  814. }
  815. withLocalEvents:NO];
  816. NSArray *expected = @[ [NSNull null] ];
  817. XCTAssertTrue([values isEqualToArray:expected], @"Should not have gotten any values yet");
  818. [self waitUntil:^BOOL {
  819. return done;
  820. }];
  821. expected = @[ [NSNull null], @"hello!" ];
  822. XCTAssertTrue([values isEqualToArray:expected], @"Should have the new value now");
  823. }
  824. - (void)testTransactionWithoutLocalEvents2 {
  825. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  826. FIRDatabaseReference *ref1 = refs.one;
  827. FIRDatabaseReference *ref2 = refs.two;
  828. int SETS = 4;
  829. [ref1.repo setHijackHash:YES];
  830. NSMutableArray *events = [[NSMutableArray alloc] init];
  831. [ref1 setValue:@0];
  832. [ref1 observeEventType:FIRDataEventTypeValue
  833. withBlock:^(FIRDataSnapshot *snapshot) {
  834. [events addObject:[snapshot value]];
  835. }];
  836. [self waitUntil:^BOOL {
  837. return events.count > 0;
  838. }];
  839. NSArray *expected = @[ @0 ];
  840. XCTAssertTrue([events isEqualToArray:expected], @"Got initial set");
  841. __block int retries = 0;
  842. __block BOOL done = NO;
  843. [ref1
  844. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  845. retries++;
  846. id val = [currentData value];
  847. NSNumber *num = @0;
  848. if (val != [NSNull null]) {
  849. num = val;
  850. }
  851. int eventCount = [num intValue];
  852. if (eventCount == SETS - 1) {
  853. [ref1.repo setHijackHash:NO];
  854. }
  855. [currentData setValue:@"txn result"];
  856. return [FIRTransactionResult successWithValue:currentData];
  857. }
  858. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  859. XCTAssertTrue(error == nil, @"Should not be an error");
  860. XCTAssertTrue(committed, @"Committed");
  861. XCTAssertTrue([[snapshot value] isEqualToString:@"txn result"], @"got correct snapshot");
  862. done = YES;
  863. }
  864. withLocalEvents:NO];
  865. // Meanwhile, do sets from the second connection
  866. for (int i = 0; i < SETS; ++i) {
  867. __block BOOL setDone = NO;
  868. [ref2 setValue:[NSNumber numberWithInt:i]
  869. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  870. setDone = YES;
  871. }];
  872. [self waitUntil:^BOOL {
  873. return setDone;
  874. }];
  875. }
  876. [self waitUntil:^BOOL {
  877. return done;
  878. }];
  879. XCTAssertTrue(retries > 0, @"Transaction should have retried");
  880. XCTAssertEqualObjects([events lastObject], @"txn result",
  881. @"Final value matches expected value from txn");
  882. }
  883. // Skipping test of calling transaction from value callback. Since all api calls are async on iOS,
  884. // nested calls are not a problem.
  885. - (void)testTransactionRevertsDataWhenAddADeeperListen {
  886. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  887. FIRDatabaseReference *ref1 = refs.one;
  888. FIRDatabaseReference *ref2 = refs.two;
  889. __block BOOL done = NO;
  890. [[ref1 child:@"y"] setValue:@"test"
  891. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  892. [ref2 runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  893. if (currentData.value == [NSNull null]) {
  894. [[currentData childDataByAppendingPath:@"x"] setValue:@5];
  895. return [FIRTransactionResult successWithValue:currentData];
  896. } else {
  897. return [FIRTransactionResult abort];
  898. }
  899. }];
  900. [[ref2 child:@"y"] observeEventType:FIRDataEventTypeValue
  901. withBlock:^(FIRDataSnapshot *snapshot) {
  902. if ([snapshot.value isEqual:@"test"]) {
  903. done = YES;
  904. }
  905. }];
  906. }];
  907. [self waitUntil:^BOOL {
  908. return done;
  909. }];
  910. }
  911. - (void)testTransactionWithIntegerKeys {
  912. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  913. __block BOOL done = NO;
  914. NSDictionary *toSet = @{@"1" : @1, @"5" : @5, @"10" : @10, @"20" : @20};
  915. [ref setValue:toSet
  916. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  917. [ref
  918. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  919. [currentData setValue:@42];
  920. return [FIRTransactionResult successWithValue:currentData];
  921. }
  922. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  923. XCTAssertNil(error, @"Error should be nil.");
  924. XCTAssertTrue(committed, @"Transaction should have committed.");
  925. done = YES;
  926. }];
  927. }];
  928. [self waitUntil:^BOOL {
  929. return done;
  930. }];
  931. }
  932. // https://app.asana.com/0/5673976843758/9259161251948
  933. - (void)testBubbleAppTransactionBug {
  934. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  935. __block BOOL done = NO;
  936. [[ref child:@"a"]
  937. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  938. [currentData setValue:@1];
  939. return [FIRTransactionResult successWithValue:currentData];
  940. }
  941. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
  942. }];
  943. [[ref child:@"a"]
  944. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  945. NSNumber *val = currentData.value;
  946. NSNumber *new = [ NSNumber numberWithInt : (val.intValue + 42) ];
  947. [currentData setValue:new];
  948. return [FIRTransactionResult successWithValue:currentData];
  949. }
  950. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
  951. }];
  952. [[ref child:@"b"]
  953. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  954. [currentData setValue:@7];
  955. return [FIRTransactionResult successWithValue:currentData];
  956. }
  957. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
  958. }];
  959. [ref
  960. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  961. NSNumber *a = [currentData childDataByAppendingPath:@"a"].value;
  962. NSNumber *b = [currentData childDataByAppendingPath:@"b"].value;
  963. NSNumber *new = [ NSNumber numberWithInt : a.intValue + b.intValue ];
  964. [currentData setValue:new];
  965. return [FIRTransactionResult successWithValue:currentData];
  966. }
  967. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  968. XCTAssertNil(error, @"Error should be nil.");
  969. XCTAssertTrue(committed, @"Committed should be true.");
  970. XCTAssertEqualObjects(@50, snapshot.value, @"Result should be 50.");
  971. done = YES;
  972. }];
  973. [self waitUntil:^BOOL {
  974. return done;
  975. }];
  976. }
  977. // If we have cached data, transactions shouldn't run on null.
  978. - (void)testTransactionsAreRunInitiallyOnCurrentlyCachedData {
  979. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  980. id initialData = @{@"a" : @"a-val", @"b" : @"b-val"};
  981. __block BOOL done = NO;
  982. __weak FIRDatabaseReference *weakRef = ref;
  983. [ref setValue:initialData
  984. withCompletionBlock:^(NSError *error, FIRDatabaseReference *r) {
  985. [weakRef observeEventType:FIRDataEventTypeValue
  986. withBlock:^(FIRDataSnapshot *snapshot) {
  987. [weakRef runTransactionBlock:^FIRTransactionResult *(
  988. FIRMutableData *currentData) {
  989. XCTAssertEqualObjects(currentData.value, initialData,
  990. @"Should be initial data.");
  991. done = YES;
  992. return [FIRTransactionResult abort];
  993. }];
  994. }];
  995. }];
  996. [self waitUntil:^BOOL {
  997. return done;
  998. }];
  999. }
  1000. - (void)testMultipleLevels {
  1001. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  1002. __block BOOL done = NO;
  1003. [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1004. return [FIRTransactionResult successWithValue:currentData];
  1005. }];
  1006. [[ref child:@"a"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1007. return [FIRTransactionResult successWithValue:currentData];
  1008. }];
  1009. [[ref child:@"b"] runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1010. return [FIRTransactionResult successWithValue:currentData];
  1011. }];
  1012. [ref
  1013. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1014. return [FIRTransactionResult successWithValue:currentData];
  1015. }
  1016. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1017. done = YES;
  1018. }];
  1019. WAIT_FOR(done);
  1020. }
  1021. - (void)testLocalServerValuesEventuallyButNotImmediatelyMatchServerWithTxns {
  1022. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  1023. FIRDatabaseReference *writer = refs.one;
  1024. FIRDatabaseReference *reader = refs.two;
  1025. __block int done = 0;
  1026. NSMutableArray *readSnaps = [[NSMutableArray alloc] init];
  1027. NSMutableArray *writeSnaps = [[NSMutableArray alloc] init];
  1028. [reader observeEventType:FIRDataEventTypeValue
  1029. withBlock:^(FIRDataSnapshot *snapshot) {
  1030. if ([snapshot value] != [NSNull null]) {
  1031. [readSnaps addObject:snapshot];
  1032. if (readSnaps.count == 1) {
  1033. done += 1;
  1034. }
  1035. }
  1036. }];
  1037. [writer observeEventType:FIRDataEventTypeValue
  1038. withBlock:^(FIRDataSnapshot *snapshot) {
  1039. if ([snapshot value] != [NSNull null]) {
  1040. [writeSnaps addObject:snapshot];
  1041. if (writeSnaps.count == 2) {
  1042. done += 1;
  1043. }
  1044. }
  1045. }];
  1046. [writer
  1047. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1048. [currentData setValue:[FIRServerValue timestamp]];
  1049. [currentData setPriority:[FIRServerValue timestamp]];
  1050. return [FIRTransactionResult successWithValue:currentData];
  1051. }
  1052. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot){
  1053. }];
  1054. [self waitUntil:^BOOL {
  1055. return done == 2;
  1056. }];
  1057. XCTAssertEqual((unsigned long)[readSnaps count], (unsigned long)1,
  1058. @"Should have received one snapshot on reader");
  1059. XCTAssertEqual((unsigned long)[writeSnaps count], (unsigned long)2,
  1060. @"Should have received two snapshots on writer");
  1061. FIRDataSnapshot *firstReadSnap = [readSnaps objectAtIndex:0];
  1062. FIRDataSnapshot *firstWriteSnap = [writeSnaps objectAtIndex:0];
  1063. FIRDataSnapshot *secondWriteSnap = [writeSnaps objectAtIndex:1];
  1064. NSNumber *now = [NSNumber numberWithDouble:round([[NSDate date] timeIntervalSince1970] * 1000)];
  1065. XCTAssertTrue([now doubleValue] - [firstWriteSnap.value doubleValue] < 2000,
  1066. @"Should have received a local event with a value close to timestamp");
  1067. XCTAssertTrue([now doubleValue] - [firstWriteSnap.priority doubleValue] < 2000,
  1068. @"Should have received a local event with a priority close to timestamp");
  1069. XCTAssertTrue([now doubleValue] - [secondWriteSnap.value doubleValue] < 2000,
  1070. @"Should have received a server event with a value close to timestamp");
  1071. XCTAssertTrue([now doubleValue] - [secondWriteSnap.priority doubleValue] < 2000,
  1072. @"Should have received a server event with a priority close to timestamp");
  1073. XCTAssertFalse([firstWriteSnap value] == [secondWriteSnap value],
  1074. @"Initial and future writer values should be different");
  1075. XCTAssertFalse([firstWriteSnap priority] == [secondWriteSnap priority],
  1076. @"Initial and future writer priorities should be different");
  1077. XCTAssertEqualObjects(firstReadSnap.value, secondWriteSnap.value,
  1078. @"Eventual reader and writer values should be equal");
  1079. XCTAssertEqualObjects(firstReadSnap.priority, secondWriteSnap.priority,
  1080. @"Eventual reader and writer priorities should be equal");
  1081. }
  1082. - (void)testTransactionWithQueryListen {
  1083. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  1084. __block BOOL done = NO;
  1085. [ref setValue:@{@"a" : @1, @"b" : @2}
  1086. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  1087. [[ref queryLimitedToFirst:1]
  1088. observeEventType:FIRDataEventTypeChildAdded
  1089. andPreviousSiblingKeyWithBlock:^(FIRDataSnapshot *snapshot, NSString *prevName) {
  1090. }
  1091. withCancelBlock:^(NSError *error){
  1092. }];
  1093. [[ref child:@"a"]
  1094. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1095. return [FIRTransactionResult successWithValue:currentData];
  1096. }
  1097. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1098. XCTAssertNil(error, @"This transaction should not have an error");
  1099. XCTAssertTrue(committed, @"Should not have aborted");
  1100. XCTAssertEqualObjects([snapshot value], @1,
  1101. @"Transaction value should match initial set");
  1102. done = YES;
  1103. }];
  1104. }];
  1105. WAIT_FOR(done);
  1106. }
  1107. - (void)testTransactionDoesNotPickUpCachedDataFromPreviousOnce {
  1108. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  1109. FIRDatabaseReference *me = refs.one;
  1110. FIRDatabaseReference *other = refs.two;
  1111. __block BOOL done = NO;
  1112. [me setValue:@"not null"
  1113. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  1114. done = YES;
  1115. }];
  1116. WAIT_FOR(done);
  1117. done = NO;
  1118. [me observeSingleEventOfType:FIRDataEventTypeValue
  1119. withBlock:^(FIRDataSnapshot *snapshot) {
  1120. done = YES;
  1121. }];
  1122. WAIT_FOR(done);
  1123. done = NO;
  1124. [other setValue:[NSNull null]
  1125. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  1126. done = YES;
  1127. }];
  1128. WAIT_FOR(done);
  1129. done = NO;
  1130. [me
  1131. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1132. id current = [currentData value];
  1133. if (current == [NSNull null]) {
  1134. [currentData setValue:@"it was null!"];
  1135. } else {
  1136. [currentData setValue:@"it was not null!"];
  1137. }
  1138. return [FIRTransactionResult successWithValue:currentData];
  1139. }
  1140. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1141. XCTAssertNil(error, @"This transaction should not have an error");
  1142. XCTAssertTrue(committed, @"Should not have aborted");
  1143. XCTAssertEqualObjects([snapshot value], @"it was null!",
  1144. @"Transaction value should match remote null set");
  1145. done = YES;
  1146. }];
  1147. WAIT_FOR(done);
  1148. }
  1149. - (void)testTransactionDoesNotPickUpCachedDataFromPreviousTransaction {
  1150. FTupleFirebase *refs = [FTestHelpers getRandomNodePair];
  1151. FIRDatabaseReference *me = refs.one;
  1152. FIRDatabaseReference *other = refs.two;
  1153. __block BOOL done = NO;
  1154. [me
  1155. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1156. [currentData setValue:@"not null"];
  1157. return [FIRTransactionResult successWithValue:currentData];
  1158. }
  1159. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1160. XCTAssertNil(error, @"This transaction should not have an error");
  1161. XCTAssertTrue(committed, @"Should not have aborted");
  1162. done = YES;
  1163. }];
  1164. WAIT_FOR(done);
  1165. done = NO;
  1166. [other setValue:[NSNull null]
  1167. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  1168. done = YES;
  1169. }];
  1170. WAIT_FOR(done);
  1171. done = NO;
  1172. [me
  1173. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1174. id current = [currentData value];
  1175. if (current == [NSNull null]) {
  1176. [currentData setValue:@"it was null!"];
  1177. } else {
  1178. [currentData setValue:@"it was not null!"];
  1179. }
  1180. return [FIRTransactionResult successWithValue:currentData];
  1181. }
  1182. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1183. XCTAssertNil(error, @"This transaction should not have an error");
  1184. XCTAssertTrue(committed, @"Should not have aborted");
  1185. XCTAssertEqualObjects([snapshot value], @"it was null!",
  1186. @"Transaction value should match remote null set");
  1187. done = YES;
  1188. }];
  1189. WAIT_FOR(done);
  1190. }
  1191. - (void)testTransactionOnQueriedLocationDoesntRunInitiallyOnNull {
  1192. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  1193. __block BOOL txnDone = NO;
  1194. [self waitForCompletionOf:[ref childByAutoId] setValue:@{@"a" : @1, @"b" : @2}];
  1195. [[ref queryLimitedToFirst:1]
  1196. observeEventType:FIRDataEventTypeChildAdded
  1197. withBlock:^(FIRDataSnapshot *snapshot) {
  1198. [snapshot.ref
  1199. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1200. id expected = @{@"a" : @1, @"b" : @2};
  1201. XCTAssertEqualObjects(currentData.value, expected, @"");
  1202. [currentData setValue:[NSNull null]];
  1203. return [FIRTransactionResult successWithValue:currentData];
  1204. }
  1205. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1206. XCTAssertNil(error, @"");
  1207. XCTAssertTrue(committed, @"");
  1208. XCTAssertEqualObjects(snapshot.value, [NSNull null], @"");
  1209. txnDone = YES;
  1210. }];
  1211. }];
  1212. WAIT_FOR(txnDone);
  1213. }
  1214. - (void)testTransactionsRaiseCorrectChildChangedEventsOnQueries {
  1215. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  1216. __block BOOL txnDone = NO;
  1217. NSMutableArray *snapshots = [[NSMutableArray alloc] init];
  1218. [self waitForCompletionOf:ref setValue:@{@"foo" : @{@"value" : @1}}];
  1219. FIRDatabaseQuery *query = [ref queryEndingAtValue:@(DBL_MIN)];
  1220. [query observeEventType:FIRDataEventTypeChildAdded
  1221. withBlock:^(FIRDataSnapshot *snapshot) {
  1222. [snapshots addObject:snapshot];
  1223. }];
  1224. [query observeEventType:FIRDataEventTypeChildChanged
  1225. withBlock:^(FIRDataSnapshot *snapshot) {
  1226. [snapshots addObject:snapshot];
  1227. }];
  1228. [[ref child:@"foo"]
  1229. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1230. [[currentData childDataByAppendingPath:@"value"] setValue:@2];
  1231. return [FIRTransactionResult successWithValue:currentData];
  1232. }
  1233. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1234. XCTAssertNil(error, @"");
  1235. XCTAssertTrue(committed, @"");
  1236. txnDone = YES;
  1237. }
  1238. withLocalEvents:NO];
  1239. WAIT_FOR(txnDone);
  1240. XCTAssertTrue(snapshots.count == 2, @"");
  1241. FIRDataSnapshot *addedSnapshot = snapshots[0];
  1242. XCTAssertEqualObjects(addedSnapshot.key, @"foo", @"");
  1243. XCTAssertEqualObjects(addedSnapshot.value, @{@"value" : @1}, @"");
  1244. FIRDataSnapshot *changedSnapshot = snapshots[1];
  1245. XCTAssertEqualObjects(changedSnapshot.key, @"foo", @"");
  1246. XCTAssertEqualObjects(changedSnapshot.value, @{@"value" : @2}, @"");
  1247. }
  1248. - (void)testTransactionsUseLocalMerges {
  1249. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  1250. __block BOOL txnDone = NO;
  1251. [ref updateChildValues:@{@"foo" : @"bar"}];
  1252. [[ref child:@"foo"]
  1253. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1254. XCTAssertEqualObjects(currentData.value, @"bar",
  1255. @"Transaction value matches local updates");
  1256. return [FIRTransactionResult successWithValue:currentData];
  1257. }
  1258. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1259. XCTAssertNil(error, @"");
  1260. XCTAssertTrue(committed, @"");
  1261. txnDone = YES;
  1262. }];
  1263. WAIT_FOR(txnDone);
  1264. }
  1265. // See https://app.asana.com/0/15566422264127/23303789496881
  1266. - (void)testOutOfOrderRemoveWritesAreHandledCorrectly {
  1267. FIRDatabaseReference *ref = [FTestHelpers getRandomNode];
  1268. [ref setValue:@{@"foo" : @"bar"}];
  1269. [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1270. [currentData setValue:@"transaction-1"];
  1271. return [FIRTransactionResult successWithValue:currentData];
  1272. }];
  1273. [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1274. [currentData setValue:@"transaction-2"];
  1275. return [FIRTransactionResult successWithValue:currentData];
  1276. }];
  1277. __block BOOL done = NO;
  1278. // This will trigger an abort of the transaction which should not cause the client to crash
  1279. [ref updateChildValues:@{@"qux" : @"quu"}
  1280. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  1281. XCTAssertNil(error);
  1282. done = YES;
  1283. }];
  1284. WAIT_FOR(done);
  1285. }
  1286. - (void)testUnsentTransactionsAreNotCancelledOnDisconnect {
  1287. // Hack: To trigger us to disconnect before restoring state, we inject a bad auth token.
  1288. // In real-world usage the much more common case is that we get redirected to a different
  1289. // server, but that's harder to manufacture from a test.
  1290. NSString *configName = @"testUnsentTransactionsAreNotCancelledOnDisconnect";
  1291. FIRDatabaseConfig *config = [FTestHelpers configForName:configName];
  1292. config.contextProvider = [[FIROneBadTokenProvider alloc] init];
  1293. // Queue a transaction offline.
  1294. FIRDatabaseReference *root = [[FTestHelpers databaseForConfig:config] reference];
  1295. [root.database goOffline];
  1296. __block BOOL done = NO;
  1297. [[root childByAutoId]
  1298. runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  1299. [currentData setValue:@0];
  1300. return [FIRTransactionResult successWithValue:currentData];
  1301. }
  1302. andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  1303. XCTAssertNil(error);
  1304. XCTAssertTrue(committed);
  1305. done = YES;
  1306. }];
  1307. [root.database goOnline];
  1308. WAIT_FOR(done);
  1309. }
  1310. @end