FTransactionTest.m 51 KB

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