FRealtime.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  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 "FRealtime.h"
  17. #import "FIRDatabaseConfig_Private.h"
  18. #import "FParsedUrl.h"
  19. #import "FRepoManager.h"
  20. #import "FTestHelpers.h"
  21. #import "FTupleFirebase.h"
  22. #import "FUtilities.h"
  23. @implementation FRealtime
  24. - (void)testUrlParsing {
  25. FParsedUrl *parsed = [FUtilities parseUrl:@"http://www.example.com:9000"];
  26. XCTAssertTrue([[parsed.path description] isEqualToString:@"/"], @"Got correct path");
  27. XCTAssertTrue([parsed.repoInfo.host isEqualToString:@"www.example.com:9000"],
  28. @"Got correct host");
  29. XCTAssertTrue([parsed.repoInfo.internalHost isEqualToString:@"www.example.com:9000"],
  30. @"Got correct host");
  31. XCTAssertFalse(parsed.repoInfo.secure, @"Should not be secure, there's a port");
  32. parsed = [FUtilities parseUrl:@"http://www.firebaseio.com/foo/bar"];
  33. XCTAssertTrue([[parsed.path description] isEqualToString:@"/foo/bar"], @"Got correct path");
  34. XCTAssertTrue([parsed.repoInfo.host isEqualToString:@"www.firebaseio.com"], @"Got correct host");
  35. XCTAssertTrue([parsed.repoInfo.internalHost isEqualToString:@"www.firebaseio.com"],
  36. @"Got correct host");
  37. XCTAssertTrue(parsed.repoInfo.secure, @"Should be secure, there's no port");
  38. }
  39. - (void)testCachingRedirects {
  40. NSString *host = @"host.example.com";
  41. NSString *host2 = @"host2.example.com";
  42. NSString *internalHost = @"internal.example.com";
  43. NSString *internalHost2 = @"internal2.example.com";
  44. // Set host on first repo info
  45. FRepoInfo *repoInfo = [[FRepoInfo alloc] initWithHost:host isSecure:YES withNamespace:host];
  46. XCTAssertTrue([repoInfo.host isEqualToString:host], @"Got correct host");
  47. XCTAssertTrue([repoInfo.internalHost isEqualToString:host], @"Got correct host");
  48. // Set internal host on first repo info
  49. repoInfo.internalHost = internalHost;
  50. XCTAssertTrue([repoInfo.host isEqualToString:host], @"Got correct host");
  51. XCTAssertTrue([repoInfo.internalHost isEqualToString:internalHost], @"Got correct host");
  52. // Set up a second unrelated repo info to make sure caching is keyspaced properly
  53. FRepoInfo *repoInfo2 = [[FRepoInfo alloc] initWithHost:host2 isSecure:YES withNamespace:host2];
  54. XCTAssertTrue([repoInfo2.host isEqualToString:host2], @"Got correct host");
  55. XCTAssertTrue([repoInfo2.internalHost isEqualToString:host2], @"Got correct host");
  56. repoInfo2.internalHost = internalHost2;
  57. XCTAssertTrue([repoInfo2.internalHost isEqualToString:internalHost2], @"Got correct host");
  58. // Setting host on this repo info should also set the right internal host
  59. FRepoInfo *repoInfoCached = [[FRepoInfo alloc] initWithHost:host isSecure:YES withNamespace:host];
  60. XCTAssertTrue([repoInfoCached.host isEqualToString:host], @"Got correct host");
  61. XCTAssertTrue([repoInfoCached.internalHost isEqualToString:internalHost], @"Got correct host");
  62. [repoInfo clearInternalHostCache];
  63. [repoInfo2 clearInternalHostCache];
  64. [repoInfoCached clearInternalHostCache];
  65. XCTAssertTrue([repoInfo.internalHost isEqualToString:host], @"Got correct host");
  66. XCTAssertTrue([repoInfo2.internalHost isEqualToString:host2], @"Got correct host");
  67. XCTAssertTrue([repoInfoCached.internalHost isEqualToString:host], @"Got correct host");
  68. }
  69. - (void)testOnDisconnectSetWorks {
  70. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  71. FIRDatabaseConfig *readerCfg = [FTestHelpers configForName:@"reader"];
  72. FIRDatabaseReference *writer =
  73. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  74. FIRDatabaseReference *reader =
  75. [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
  76. __block NSNumber *readValue = @0;
  77. __block NSNumber *writeValue = @0;
  78. [[reader child:@"disconnected"] observeEventType:FIRDataEventTypeValue
  79. withBlock:^(FIRDataSnapshot *snapshot) {
  80. NSNumber *val = [snapshot value];
  81. if (![val isEqual:[NSNull null]]) {
  82. readValue = val;
  83. }
  84. }];
  85. [[writer child:@"disconnected"] observeEventType:FIRDataEventTypeValue
  86. withBlock:^(FIRDataSnapshot *snapshot) {
  87. id val = [snapshot value];
  88. if (val != [NSNull null]) {
  89. writeValue = val;
  90. }
  91. }];
  92. [writer child:@"hello"];
  93. __block BOOL ready = NO;
  94. [[writer child:@"disconnected"]
  95. onDisconnectSetValue:@1
  96. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  97. ready = YES;
  98. }];
  99. [self waitUntil:^BOOL {
  100. return ready;
  101. }];
  102. [writer child:@"s"];
  103. ready = NO;
  104. [[writer child:@"disconnected"]
  105. onDisconnectSetValue:@2
  106. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  107. ready = YES;
  108. }];
  109. [self waitUntil:^BOOL {
  110. return ready;
  111. }];
  112. [FRepoManager interrupt:writerCfg];
  113. [self waitUntil:^BOOL {
  114. return [@2 isEqualToNumber:readValue] && [@2 isEqualToNumber:writeValue];
  115. }];
  116. [FRepoManager interrupt:readerCfg];
  117. // cleanup
  118. [FRepoManager disposeRepos:writerCfg];
  119. [FRepoManager disposeRepos:readerCfg];
  120. }
  121. - (void)testOnDisconnectSetWithPriorityWorks {
  122. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  123. FIRDatabaseConfig *readerCfg = [FTestHelpers configForName:@"reader"];
  124. FIRDatabaseReference *writer =
  125. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  126. FIRDatabaseReference *reader =
  127. [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
  128. __block BOOL sawNewValue = NO;
  129. __block BOOL writerSawNewValue = NO;
  130. [[reader child:@"disconnected"] observeEventType:FIRDataEventTypeValue
  131. withBlock:^(FIRDataSnapshot *snapshot) {
  132. id val = snapshot.value;
  133. id pri = snapshot.priority;
  134. if (val != [NSNull null] && pri != [NSNull null]) {
  135. sawNewValue = [(NSNumber *)val boolValue] &&
  136. [pri isEqualToString:@"abcd"];
  137. }
  138. }];
  139. [[writer child:@"disconnected"] observeEventType:FIRDataEventTypeValue
  140. withBlock:^(FIRDataSnapshot *snapshot) {
  141. id val = [snapshot value];
  142. id pri = snapshot.priority;
  143. if (val != [NSNull null] && pri != [NSNull null]) {
  144. writerSawNewValue = [(NSNumber *)val boolValue] &&
  145. [pri isEqualToString:@"abcd"];
  146. }
  147. }];
  148. __block BOOL ready = NO;
  149. [[writer child:@"disconnected"]
  150. onDisconnectSetValue:@YES
  151. andPriority:@"abcd"
  152. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  153. ready = YES;
  154. }];
  155. [self waitUntil:^BOOL {
  156. return ready;
  157. }];
  158. [FRepoManager interrupt:writerCfg];
  159. [self waitUntil:^BOOL {
  160. return sawNewValue && writerSawNewValue;
  161. }];
  162. [FRepoManager interrupt:readerCfg];
  163. // cleanup
  164. [FRepoManager disposeRepos:writerCfg];
  165. [FRepoManager disposeRepos:readerCfg];
  166. }
  167. - (void)testOnDisconnectRemoveWorks {
  168. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  169. FIRDatabaseConfig *readerCfg = [FTestHelpers configForName:@"reader"];
  170. FIRDatabaseReference *writer =
  171. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  172. FIRDatabaseReference *reader =
  173. [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
  174. __block BOOL ready = NO;
  175. [[writer child:@"foo"] setValue:@"bar"
  176. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  177. ready = YES;
  178. }];
  179. [self waitUntil:^BOOL {
  180. return ready;
  181. }];
  182. __block BOOL sawRemove = NO;
  183. __block BOOL writerSawRemove = NO;
  184. [[reader child:@"foo"] observeEventType:FIRDataEventTypeValue
  185. withBlock:^(FIRDataSnapshot *snapshot) {
  186. sawRemove = [[NSNull null] isEqual:snapshot.value];
  187. }];
  188. [[writer child:@"foo"] observeEventType:FIRDataEventTypeValue
  189. withBlock:^(FIRDataSnapshot *snapshot) {
  190. writerSawRemove = [[NSNull null] isEqual:snapshot.value];
  191. }];
  192. ready = NO;
  193. [[writer child:@"foo"]
  194. onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  195. ready = YES;
  196. }];
  197. [self waitUntil:^BOOL {
  198. return ready;
  199. }];
  200. [FRepoManager interrupt:writerCfg];
  201. [self waitUntil:^BOOL {
  202. return sawRemove && writerSawRemove;
  203. }];
  204. [FRepoManager interrupt:readerCfg];
  205. // cleanup
  206. [FRepoManager disposeRepos:writerCfg];
  207. [FRepoManager disposeRepos:readerCfg];
  208. }
  209. - (void)testOnDisconnectUpdateWorks {
  210. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  211. FIRDatabaseConfig *readerCfg = [FTestHelpers configForName:@"reader"];
  212. FIRDatabaseReference *writer =
  213. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  214. FIRDatabaseReference *reader =
  215. [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
  216. [self waitForCompletionOf:[writer child:@"foo"] setValue:@{@"bar" : @"a", @"baz" : @"b"}];
  217. __block BOOL sawNewValue = NO;
  218. __block BOOL writerSawNewValue = NO;
  219. [[reader child:@"foo"] observeEventType:FIRDataEventTypeValue
  220. withBlock:^(FIRDataSnapshot *snapshot) {
  221. NSDictionary *val = [snapshot value];
  222. if (val) {
  223. sawNewValue = [@{@"bar" : @"a", @"baz" : @"c", @"bat" : @"d"}
  224. isEqualToDictionary:val];
  225. }
  226. }];
  227. [[writer child:@"foo"]
  228. observeEventType:FIRDataEventTypeValue
  229. withBlock:^(FIRDataSnapshot *snapshot) {
  230. NSDictionary *val = [snapshot value];
  231. if (val) {
  232. writerSawNewValue =
  233. [@{@"bar" : @"a", @"baz" : @"c", @"bat" : @"d"} isEqualToDictionary:val];
  234. }
  235. }];
  236. __block BOOL ready = NO;
  237. [[writer child:@"foo"]
  238. onDisconnectUpdateChildValues:@{@"baz" : @"c", @"bat" : @"d"}
  239. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  240. ready = YES;
  241. }];
  242. [self waitUntil:^BOOL {
  243. return ready;
  244. }];
  245. [FRepoManager interrupt:writerCfg];
  246. [self waitUntil:^BOOL {
  247. return sawNewValue && writerSawNewValue;
  248. }];
  249. [FRepoManager interrupt:readerCfg];
  250. // cleanup
  251. [FRepoManager disposeRepos:writerCfg];
  252. [FRepoManager disposeRepos:readerCfg];
  253. }
  254. - (void)testOnDisconnectTriggersSingleLocalValueEventForWriter {
  255. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  256. FIRDatabaseReference *writer =
  257. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  258. __block int calls = 0;
  259. [writer observeEventType:FIRDataEventTypeValue
  260. withBlock:^(FIRDataSnapshot *snapshot) {
  261. calls++;
  262. if (calls == 2) {
  263. // second call, verify the data
  264. NSDictionary *val = [snapshot value];
  265. NSDictionary *expected = @{@"foo" : @{@"bar" : @"a", @"bam" : @"c"}};
  266. XCTAssertTrue([val isEqualToDictionary:expected],
  267. @"Got all of the updates in one");
  268. } else if (calls > 2) {
  269. XCTFail(@"Extra calls");
  270. }
  271. }];
  272. [self waitUntil:^BOOL {
  273. return calls == 1;
  274. }];
  275. __block BOOL done = NO;
  276. FIRDatabaseReference *child = [writer child:@"foo"];
  277. [child onDisconnectSetValue:@{@"bar" : @"a", @"baz" : @"b"}];
  278. [child onDisconnectUpdateChildValues:@{@"bam" : @"c"}];
  279. [[child child:@"baz"]
  280. onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  281. done = YES;
  282. }];
  283. [self waitUntil:^BOOL {
  284. return done;
  285. }];
  286. [FRepoManager interrupt:writerCfg];
  287. [self waitUntil:^BOOL {
  288. return calls == 2;
  289. }];
  290. // cleanup
  291. [FRepoManager disposeRepos:writerCfg];
  292. }
  293. - (void)testOnDisconnectTriggersSingleLocalValueEventForReader {
  294. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  295. FIRDatabaseReference *reader = [FTestHelpers getRandomNode];
  296. FIRDatabaseReference *writer =
  297. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] child:reader.key];
  298. __block int calls = 0;
  299. [reader observeEventType:FIRDataEventTypeValue
  300. withBlock:^(FIRDataSnapshot *snapshot) {
  301. calls++;
  302. if (calls == 2) {
  303. // second call, verify the data
  304. NSDictionary *val = [snapshot value];
  305. NSDictionary *expected = @{@"foo" : @{@"bar" : @"a", @"bam" : @"c"}};
  306. XCTAssertTrue([val isEqualToDictionary:expected],
  307. @"Got all of the updates in one");
  308. } else if (calls > 2) {
  309. XCTFail(@"Extra calls");
  310. }
  311. }];
  312. [self waitUntil:^BOOL {
  313. return calls == 1;
  314. }];
  315. __block BOOL done = NO;
  316. FIRDatabaseReference *child = [writer child:@"foo"];
  317. [child onDisconnectSetValue:@{@"bar" : @"a", @"baz" : @"b"}];
  318. [child onDisconnectUpdateChildValues:@{@"bam" : @"c"}];
  319. [[child child:@"baz"]
  320. onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  321. done = YES;
  322. }];
  323. [self waitUntil:^BOOL {
  324. return done;
  325. }];
  326. [FRepoManager interrupt:writerCfg];
  327. [self waitUntil:^BOOL {
  328. return calls == 2;
  329. }];
  330. // cleanup
  331. [FRepoManager disposeRepos:writerCfg];
  332. }
  333. - (void)testOnDisconnectTriggersSingleLocalValueEventForWriterWithQuery {
  334. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  335. FIRDatabaseReference *writer =
  336. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  337. __block int calls = 0;
  338. [[[writer child:@"foo"] queryLimitedToLast:2]
  339. observeEventType:FIRDataEventTypeValue
  340. withBlock:^(FIRDataSnapshot *snapshot) {
  341. calls++;
  342. if (calls == 2) {
  343. // second call, verify the data
  344. NSDictionary *val = [snapshot value];
  345. NSDictionary *expected = @{@"bar" : @"a", @"bam" : @"c"};
  346. XCTAssertTrue([val isEqualToDictionary:expected],
  347. @"Got all of the updates in one");
  348. } else if (calls > 2) {
  349. XCTFail(@"Extra calls");
  350. }
  351. }];
  352. [self waitUntil:^BOOL {
  353. return calls == 1;
  354. }];
  355. __block BOOL done = NO;
  356. FIRDatabaseReference *child = [writer child:@"foo"];
  357. [child onDisconnectSetValue:@{@"bar" : @"a", @"baz" : @"b"}];
  358. [child onDisconnectUpdateChildValues:@{@"bam" : @"c"}];
  359. [[child child:@"baz"]
  360. onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  361. done = YES;
  362. }];
  363. [self waitUntil:^BOOL {
  364. return done;
  365. }];
  366. [FRepoManager interrupt:writerCfg];
  367. [self waitUntil:^BOOL {
  368. return calls == 2;
  369. }];
  370. // cleanup
  371. [FRepoManager disposeRepos:writerCfg];
  372. }
  373. - (void)testOnDisconnectTriggersSingleLocalValueEventForReaderWithQuery {
  374. FIRDatabaseReference *reader = [FTestHelpers getRandomNode];
  375. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  376. FIRDatabaseReference *writer =
  377. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] child:reader.key];
  378. __block int calls = 0;
  379. [[[reader child:@"foo"] queryLimitedToLast:2]
  380. observeEventType:FIRDataEventTypeValue
  381. withBlock:^(FIRDataSnapshot *snapshot) {
  382. calls++;
  383. XCTAssertTrue([snapshot.key isEqualToString:@"foo"], @"Got the right snapshot");
  384. if (calls == 2) {
  385. // second call, verify the data
  386. NSDictionary *val = [snapshot value];
  387. NSDictionary *expected = @{@"bar" : @"a", @"bam" : @"c"};
  388. XCTAssertTrue([val isEqualToDictionary:expected],
  389. @"Got all of the updates in one");
  390. } else if (calls > 2) {
  391. XCTFail(@"Extra calls");
  392. }
  393. }];
  394. [self waitUntil:^BOOL {
  395. return calls == 1;
  396. }];
  397. __block BOOL done = NO;
  398. FIRDatabaseReference *child = [writer child:@"foo"];
  399. [child onDisconnectSetValue:@{@"bar" : @"a", @"baz" : @"b"}];
  400. [child onDisconnectUpdateChildValues:@{@"bam" : @"c"}];
  401. [[child child:@"baz"]
  402. onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  403. done = YES;
  404. }];
  405. [self waitUntil:^BOOL {
  406. return done;
  407. }];
  408. [FRepoManager interrupt:writerCfg];
  409. [self waitUntil:^BOOL {
  410. return calls == 2;
  411. }];
  412. // cleanup
  413. [FRepoManager disposeRepos:writerCfg];
  414. }
  415. - (void)testOnDisconnectDeepMergeTriggersOnlyOneValueEventForReaderWithQuery {
  416. FIRDatabaseReference *reader = [FTestHelpers getRandomNode];
  417. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  418. FIRDatabaseReference *writer =
  419. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  420. __block BOOL done = NO;
  421. NSDictionary *toSet =
  422. @{@"a" : @1, @"b" : @{@"c" : @YES, @"d" : @"scalar", @"e" : @{@"f" : @"hooray"}}};
  423. [writer setValue:toSet];
  424. [[writer child:@"a"] onDisconnectSetValue:@2];
  425. [[writer child:@"b/d"]
  426. onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  427. done = YES;
  428. }];
  429. WAIT_FOR(done);
  430. __block int count = 2;
  431. [[reader queryLimitedToLast:3]
  432. observeEventType:FIRDataEventTypeValue
  433. withBlock:^(FIRDataSnapshot *snapshot) {
  434. count++;
  435. if (count == 1) {
  436. // Loaded the data, kill the writer connection
  437. [FRepoManager interrupt:writerCfg];
  438. } else if (count == 2) {
  439. NSDictionary *expected =
  440. @{@"a" : @2,
  441. @"b" : @{@"c" : @YES, @"e" : @{@"f" : @"hooray"}}};
  442. XCTAssertTrue([snapshot.value isEqualToDictionary:expected],
  443. @"Should see complete new snapshot");
  444. } else {
  445. XCTFail(@"Too many calls");
  446. }
  447. }];
  448. WAIT_FOR(count == 2);
  449. // cleanup
  450. [reader removeAllObservers];
  451. [FRepoManager disposeRepos:writerCfg];
  452. }
  453. - (void)testOnDisconnectCancelWorks {
  454. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  455. FIRDatabaseConfig *readerCfg = [FTestHelpers configForName:@"reader"];
  456. FIRDatabaseReference *writer =
  457. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  458. FIRDatabaseReference *reader =
  459. [[[FIRDatabaseReference alloc] initWithConfig:readerCfg] child:writer.key];
  460. __block BOOL ready = NO;
  461. [[writer child:@"foo"] setValue:@{@"bar" : @"a", @"baz" : @"b"}
  462. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  463. ready = YES;
  464. }];
  465. [self waitUntil:^BOOL {
  466. return ready;
  467. }];
  468. __block BOOL sawNewValue = NO;
  469. __block BOOL writerSawNewValue = NO;
  470. [[reader child:@"foo"] observeEventType:FIRDataEventTypeValue
  471. withBlock:^(FIRDataSnapshot *snapshot) {
  472. NSDictionary *val = [snapshot value];
  473. if (val) {
  474. sawNewValue = [@{@"bar" : @"a", @"baz" : @"b", @"bat" : @"d"}
  475. isEqualToDictionary:val];
  476. }
  477. }];
  478. [[writer child:@"foo"]
  479. observeEventType:FIRDataEventTypeValue
  480. withBlock:^(FIRDataSnapshot *snapshot) {
  481. NSDictionary *val = [snapshot value];
  482. if (val) {
  483. writerSawNewValue =
  484. [@{@"bar" : @"a", @"baz" : @"b", @"bat" : @"d"} isEqualToDictionary:val];
  485. }
  486. }];
  487. ready = NO;
  488. [[writer child:@"foo"] onDisconnectUpdateChildValues:@{@"baz" : @"c", @"bat" : @"d"}];
  489. [[writer child:@"foo/baz"]
  490. cancelDisconnectOperationsWithCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  491. ready = YES;
  492. }];
  493. [self waitUntil:^BOOL {
  494. return ready;
  495. }];
  496. [FRepoManager interrupt:writerCfg];
  497. [self waitUntil:^BOOL {
  498. return sawNewValue && writerSawNewValue;
  499. }];
  500. [FRepoManager interrupt:readerCfg];
  501. // cleanup
  502. [FRepoManager disposeRepos:writerCfg];
  503. [FRepoManager disposeRepos:readerCfg];
  504. }
  505. - (void)testOnDisconnectWithServerValuesWithLocalEvents {
  506. FIRDatabaseConfig *writerCfg = [FTestHelpers configForName:@"writer"];
  507. FIRDatabaseReference *node =
  508. [[[FIRDatabaseReference alloc] initWithConfig:writerCfg] childByAutoId];
  509. __block FIRDataSnapshot *snap = nil;
  510. [node observeEventType:FIRDataEventTypeValue
  511. withBlock:^(FIRDataSnapshot *snapshot) {
  512. snap = snapshot;
  513. }];
  514. NSDictionary *data = @{
  515. @"a" : @1,
  516. @"b" : @{@".value" : [FIRServerValue timestamp], @".priority" : [FIRServerValue timestamp]}
  517. };
  518. __block BOOL done = NO;
  519. [node onDisconnectSetValue:data
  520. andPriority:[FIRServerValue timestamp]
  521. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  522. done = YES;
  523. }];
  524. [self waitUntil:^BOOL {
  525. return done;
  526. }];
  527. done = NO;
  528. [node onDisconnectUpdateChildValues:@{
  529. @"a" : [FIRServerValue timestamp],
  530. @"c" : [FIRServerValue timestamp]
  531. }
  532. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  533. done = YES;
  534. }];
  535. [self waitUntil:^BOOL {
  536. return done;
  537. }];
  538. done = NO;
  539. [FRepoManager interrupt:writerCfg];
  540. [self waitUntil:^BOOL {
  541. if ([snap value] != [NSNull null]) {
  542. NSDictionary *val = [snap value];
  543. done = (val[@"a"] && val[@"b"] && val[@"c"]);
  544. }
  545. return done;
  546. }];
  547. NSDictionary *value = [snap value];
  548. NSNumber *now = [NSNumber numberWithDouble:round([[NSDate date] timeIntervalSince1970] * 1000)];
  549. NSNumber *timestamp = [snap priority];
  550. XCTAssertTrue([[snap priority] isKindOfClass:[NSNumber class]], @"Should get back number");
  551. XCTAssertTrue([now doubleValue] - [timestamp doubleValue] < 2000,
  552. @"Number should be no more than 2 seconds ago");
  553. XCTAssertEqualObjects([snap priority], [value objectForKey:@"a"],
  554. @"Should get back matching ServerValue.TIMESTAMP");
  555. XCTAssertEqualObjects([snap priority], [value objectForKey:@"b"],
  556. @"Should get back matching ServerValue.TIMESTAMP");
  557. XCTAssertEqualObjects([snap priority], [[snap childSnapshotForPath:@"b"] priority],
  558. @"Should get back matching ServerValue.TIMESTAMP");
  559. XCTAssertEqualObjects([NSNull null], [[snap childSnapshotForPath:@"d"] value],
  560. @"Should get null for cancelled child");
  561. // cleanup
  562. [FRepoManager disposeRepos:writerCfg];
  563. }
  564. @end