FRealtime.m 22 KB

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