FTransactionTest.m 53 KB

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