FRealtime.m 22 KB

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