FIRDatabaseTests.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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 <XCTest/XCTest.h>
  17. #import <FirebaseCore/FIRApp.h>
  18. #import "FIRDatabaseReference.h"
  19. #import "FIRDatabaseReference_Private.h"
  20. #import "FIRDatabase.h"
  21. #import "FIRDatabaseConfig_Private.h"
  22. #import "FIROptions.h"
  23. #import "FTestHelpers.h"
  24. #import "FMockStorageEngine.h"
  25. #import "FTestBase.h"
  26. #import "FTestHelpers.h"
  27. #import "FIRFakeApp.h"
  28. @interface FIRDatabaseTests : FTestBase
  29. @end
  30. static const NSInteger kFErrorCodeWriteCanceled = 3;
  31. static NSString *kFirebaseTestAltNamespace = @"https://foobar.firebaseio.com";
  32. @implementation FIRDatabaseTests
  33. - (void) testFIRDatabaseForNilApp {
  34. #pragma clang diagnostic push
  35. #pragma clang diagnostic ignored "-Wnonnull"
  36. XCTAssertThrowsSpecificNamed([FIRDatabase databaseForApp:nil], NSException, @"InvalidFIRApp");
  37. #pragma clang diagnostic pop
  38. }
  39. - (void) testDatabaseForApp {
  40. FIRDatabase *database = [self databaseForURL:self.databaseURL];
  41. XCTAssertEqualObjects(self.databaseURL, [database reference].URL);
  42. }
  43. - (void) testDatabaseForAppWithInvalidURLs {
  44. XCTAssertThrows([self databaseForURL:nil]);
  45. XCTAssertThrows([self databaseForURL:@"not-a-url"]);
  46. XCTAssertThrows([self databaseForURL:@"http://fblocal.com"]);
  47. XCTAssertThrows([self databaseForURL:@"http://x.example.com/paths/are/bad"]);
  48. }
  49. - (void)testDatabaseForAppWithURL {
  50. id app = [[FIRFakeApp alloc] initWithName:@"testDatabaseForAppWithURL" URL:kFirebaseTestAltNamespace];
  51. FIRDatabase *database = [FIRDatabase databaseForApp:app URL:@"http://foo.bar.com"];
  52. XCTAssertEqualObjects(@"https://foo.bar.com", [database reference].URL);
  53. }
  54. - (void)testDatabaseForAppWithURLAndPort {
  55. id app = [[FIRFakeApp alloc] initWithName:@"testDatabaseForAppWithURLAndPort"
  56. URL:kFirebaseTestAltNamespace];
  57. FIRDatabase *database = [FIRDatabase databaseForApp:app URL:@"http://foo.bar.com:80"];
  58. XCTAssertEqualObjects(@"http://foo.bar.com:80", [database reference].URL);
  59. }
  60. - (void)testDatabaseForAppWithHttpsURL {
  61. id app = [[FIRFakeApp alloc] initWithName:@"testDatabaseForAppWithHttpsURL"
  62. URL:kFirebaseTestAltNamespace];
  63. FIRDatabase *database = [FIRDatabase databaseForApp:app URL:@"https://foo.bar.com"];
  64. XCTAssertEqualObjects(@"https://foo.bar.com", [database reference].URL);
  65. }
  66. - (void)testDifferentInstanceForAppWithURL {
  67. id app = [[FIRFakeApp alloc] initWithName:@"testDifferentInstanceForAppWithURL"
  68. URL:kFirebaseTestAltNamespace];
  69. FIRDatabase *database1 = [FIRDatabase databaseForApp:app URL:@"https://foo1.bar.com"];
  70. FIRDatabase *database2 = [FIRDatabase databaseForApp:app URL:@"https://foo1.bar.com/"];
  71. FIRDatabase *database3 = [FIRDatabase databaseForApp:app URL:@"https://foo2.bar.com"];
  72. XCTAssertEqual(database1, database2);
  73. XCTAssertNotEqual(database1, database3);
  74. }
  75. - (void)testDatabaseForAppWithInvalidCustomURLs {
  76. id app = [[FIRFakeApp alloc] initWithName:@"testDatabaseForAppWithInvalidCustomURLs"
  77. URL:kFirebaseTestAltNamespace];
  78. #pragma clang diagnostic push
  79. #pragma clang diagnostic ignored "-Wnonnull"
  80. XCTAssertThrows([FIRDatabase databaseForApp:app URL:nil]);
  81. #pragma clang diagnostic pop
  82. XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"not-a-url"]);
  83. XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"http://fblocal.com"]);
  84. XCTAssertThrows([FIRDatabase databaseForApp:app URL:@"http://x.fblocal.com:9000/paths/are/bad"]);
  85. }
  86. - (void) testDeleteDatabase {
  87. // Set up a custom FIRApp with a custom database based on it.
  88. FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123abc"
  89. GCMSenderID:@"gcm_sender_id"];
  90. options.databaseURL = self.databaseURL;
  91. NSString *customAppName = @"MyCustomApp";
  92. [FIRApp configureWithName:customAppName options:options];
  93. FIRApp *customApp = [FIRApp appNamed:customAppName];
  94. FIRDatabase *customDatabase = [FIRDatabase databaseForApp:customApp];
  95. XCTAssertNotNil(customDatabase);
  96. // Delete the custom app and wait for it to be done.
  97. XCTestExpectation *customAppDeletedExpectation =
  98. [self expectationWithDescription:@"Deleting the custom app should be successful."];
  99. [customApp deleteApp:^(BOOL success) {
  100. // The app shouldn't exist anymore, ensure that the databaseForApp throws.
  101. XCTAssertThrows([FIRDatabase databaseForApp:[FIRApp appNamed:customAppName]]);
  102. [customAppDeletedExpectation fulfill];
  103. }];
  104. // Wait for the custom app to be deleted.
  105. [self waitForExpectations:@[customAppDeletedExpectation] timeout:2];
  106. // Configure the app again, then grab a reference to the database. Assert it's different.
  107. [FIRApp configureWithName:customAppName options:options];
  108. FIRApp *secondCustomApp = [FIRApp appNamed:customAppName];
  109. FIRDatabase *secondCustomDatabase = [FIRDatabase databaseForApp:secondCustomApp];
  110. XCTAssertNotNil(secondCustomDatabase);
  111. XCTAssertNotEqualObjects(customDatabase, secondCustomDatabase);
  112. }
  113. - (void) testReferenceWithPath {
  114. FIRDatabase *db = [self defaultDatabase];
  115. NSString *expectedURL = [NSString stringWithFormat:@"%@/foo", self.databaseURL];
  116. XCTAssertEqualObjects(expectedURL, [db referenceWithPath:@"foo"].URL);
  117. }
  118. - (void) testReferenceFromURLWithEmptyPath {
  119. FIRDatabaseReference *ref = [[self defaultDatabase] referenceFromURL:self.databaseURL];
  120. XCTAssertEqualObjects(self.databaseURL, ref.URL);
  121. }
  122. - (void) testReferenceFromURLWithPath {
  123. NSString *url = [NSString stringWithFormat:@"%@/foo/bar", self.databaseURL];
  124. FIRDatabaseReference *ref = [[self defaultDatabase] referenceFromURL:url];
  125. XCTAssertEqualObjects(url, ref.URL);
  126. }
  127. - (void) testReferenceFromURLWithWrongURL {
  128. NSString *url = [NSString stringWithFormat:@"%@/foo/bar", @"https://foobar.firebaseio.com"];
  129. XCTAssertThrows([[self defaultDatabase] referenceFromURL:url]);
  130. }
  131. - (void) testReferenceEqualityForFIRDatabase {
  132. FIRDatabase *db1 = [self databaseForURL:self.databaseURL name:@"db1"];
  133. FIRDatabase *db2 = [self databaseForURL:self.databaseURL name:@"db2"];
  134. FIRDatabase *altDb = [self databaseForURL:self.databaseURL name:@"altDb"];
  135. FIRDatabase *wrongHostDb = [self databaseForURL:@"http://tests.example.com"];
  136. FIRDatabaseReference *testRef1 = [db1 reference];
  137. FIRDatabaseReference *testRef2 = [db1 referenceWithPath:@"foo"];
  138. FIRDatabaseReference *testRef3 = [altDb reference];
  139. FIRDatabaseReference *testRef4 = [wrongHostDb reference];
  140. FIRDatabaseReference *testRef5 = [db2 reference];
  141. FIRDatabaseReference *testRef6 = [db2 reference];
  142. // Referential equality
  143. XCTAssertTrue(testRef1.database == testRef2.database);
  144. XCTAssertFalse(testRef1.database == testRef3.database);
  145. XCTAssertFalse(testRef1.database == testRef4.database);
  146. XCTAssertFalse(testRef1.database == testRef5.database);
  147. XCTAssertFalse(testRef1.database == testRef6.database);
  148. // references from same FIRDatabase same identical .database references.
  149. XCTAssertTrue(testRef5.database == testRef6.database);
  150. [db1 goOffline];
  151. [db2 goOffline];
  152. [altDb goOffline];
  153. [wrongHostDb goOffline];
  154. }
  155. - (FIRDatabaseReference *)rootRefWithEngine:(id<FStorageEngine>)engine name:(NSString *)name {
  156. FIRDatabaseConfig *config = [FIRDatabaseConfig configForName:name];
  157. config.persistenceEnabled = YES;
  158. config.forceStorageEngine = engine;
  159. return [[FIRDatabaseReference alloc] initWithConfig:config];
  160. }
  161. - (void) testPurgeWritesPurgesAllWrites {
  162. FMockStorageEngine *engine = [[FMockStorageEngine alloc] init];
  163. FIRDatabaseReference *ref = [self rootRefWithEngine:engine name:@"purgeWritesPurgesAllWrites"];
  164. FIRDatabase *database = ref.database;
  165. [database goOffline];
  166. [[ref childByAutoId] setValue:@"test-value-1"];
  167. [[ref childByAutoId] setValue:@"test-value-2"];
  168. [[ref childByAutoId] setValue:@"test-value-3"];
  169. [[ref childByAutoId] setValue:@"test-value-4"];
  170. [self waitForEvents:ref];
  171. XCTAssertEqual(engine.userWrites.count, (NSUInteger)4);
  172. [database purgeOutstandingWrites];
  173. [self waitForEvents:ref];
  174. XCTAssertEqual(engine.userWrites.count, (NSUInteger)0);
  175. [database goOnline];
  176. }
  177. - (void) testPurgeWritesAreCanceledInOrder {
  178. FMockStorageEngine *engine = [[FMockStorageEngine alloc] init];
  179. FIRDatabaseReference *ref = [self rootRefWithEngine:engine name:@"purgeWritesAndCanceledInOrder"];
  180. FIRDatabase *database = ref.database;
  181. [database goOffline];
  182. NSMutableArray *order = [NSMutableArray array];
  183. [[ref childByAutoId] setValue:@"test-value-1" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  184. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  185. [order addObject:@"1"];
  186. }];
  187. [[ref childByAutoId] setValue:@"test-value-2" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  188. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  189. [order addObject:@"2"];
  190. }];
  191. [[ref childByAutoId] setValue:@"test-value-3" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  192. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  193. [order addObject:@"3"];
  194. }];
  195. [[ref childByAutoId] setValue:@"test-value-4" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  196. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  197. [order addObject:@"4"];
  198. }];
  199. [self waitForEvents:ref];
  200. XCTAssertEqual(engine.userWrites.count, (NSUInteger)4);
  201. [database purgeOutstandingWrites];
  202. [self waitForEvents:ref];
  203. XCTAssertEqual(engine.userWrites.count, (NSUInteger)0);
  204. XCTAssertEqualObjects(order, (@[@"1", @"2", @"3", @"4"]));
  205. [database goOnline];
  206. }
  207. - (void)testPurgeWritesCancelsOnDisconnects {
  208. FMockStorageEngine *engine = [[FMockStorageEngine alloc] init];
  209. FIRDatabaseReference *ref = [self rootRefWithEngine:engine name:@"purgeWritesCancelsOnDisconnects"];
  210. FIRDatabase *database = ref.database;
  211. [database goOffline];
  212. NSMutableArray *events = [NSMutableArray array];
  213. [[ref childByAutoId] onDisconnectSetValue:@"test-value-1" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  214. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  215. [events addObject:@"1"];
  216. }];
  217. [[ref childByAutoId] onDisconnectSetValue:@"test-value-2" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  218. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  219. [events addObject:@"2"];
  220. }];
  221. [self waitForEvents:ref];
  222. [database purgeOutstandingWrites];
  223. [self waitForEvents:ref];
  224. XCTAssertEqualObjects(events, (@[@"1", @"2"]));
  225. }
  226. - (void) testPurgeWritesReraisesEvents {
  227. FMockStorageEngine *engine = [[FMockStorageEngine alloc] init];
  228. FIRDatabaseReference *ref = [[self rootRefWithEngine:engine name:@"purgeWritesReraiseEvents"] childByAutoId];
  229. FIRDatabase *database = ref.database;
  230. [self waitForCompletionOf:ref setValue:@{@"foo": @"foo-value", @"bar": @{@"qux": @"qux-value"}}];
  231. NSMutableArray *fooValues = [NSMutableArray array];
  232. NSMutableArray *barQuuValues = [NSMutableArray array];
  233. NSMutableArray *barQuxValues = [NSMutableArray array];
  234. NSMutableArray *cancelOrder = [NSMutableArray array];
  235. [[ref child:@"foo"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  236. [fooValues addObject:snapshot.value];
  237. }];
  238. [[ref child:@"bar/quu"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  239. [barQuuValues addObject:snapshot.value];
  240. }];
  241. [[ref child:@"bar/qux"] observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  242. [barQuxValues addObject:snapshot.value];
  243. }];
  244. [database goOffline];
  245. [[ref child:@"foo"] setValue:@"new-foo-value" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  246. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  247. // This should be after we raised events
  248. XCTAssertEqualObjects(fooValues.lastObject, @"foo-value");
  249. [cancelOrder addObject:@"foo-1"];
  250. }];
  251. [[ref child:@"bar"] updateChildValues:@{@"quu": @"quu-value", @"qux": @"new-qux-value"}
  252. withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  253. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  254. // This should be after we raised events
  255. XCTAssertEqualObjects(barQuxValues.lastObject, @"qux-value");
  256. XCTAssertEqualObjects(barQuuValues.lastObject, [NSNull null]);
  257. [cancelOrder addObject:@"bar"];
  258. }];
  259. [[ref child:@"foo"] setValue:@"newest-foo-value" withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  260. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  261. // This should be after we raised events
  262. XCTAssertEqualObjects(fooValues.lastObject, @"foo-value");
  263. [cancelOrder addObject:@"foo-2"];
  264. }];
  265. [database purgeOutstandingWrites];
  266. [self waitForEvents:ref];
  267. XCTAssertEqualObjects(cancelOrder, (@[@"foo-1", @"bar", @"foo-2"]));
  268. XCTAssertEqualObjects(fooValues, (@[@"foo-value", @"new-foo-value", @"newest-foo-value", @"foo-value"]));
  269. XCTAssertEqualObjects(barQuuValues, (@[[NSNull null], @"quu-value", [NSNull null]]));
  270. XCTAssertEqualObjects(barQuxValues, (@[@"qux-value", @"new-qux-value", @"qux-value"]));
  271. [database goOnline];
  272. // Make sure we're back online and reconnected again
  273. [self waitForRoundTrip:ref];
  274. // No events should be reraised
  275. XCTAssertEqualObjects(cancelOrder, (@[@"foo-1", @"bar", @"foo-2"]));
  276. XCTAssertEqualObjects(fooValues, (@[@"foo-value", @"new-foo-value", @"newest-foo-value", @"foo-value"]));
  277. XCTAssertEqualObjects(barQuuValues, (@[[NSNull null], @"quu-value", [NSNull null]]));
  278. XCTAssertEqualObjects(barQuxValues, (@[@"qux-value", @"new-qux-value", @"qux-value"]));
  279. }
  280. - (void)testPurgeWritesCancelsTransactions {
  281. FMockStorageEngine *engine = [[FMockStorageEngine alloc] init];
  282. FIRDatabaseReference *ref = [[self rootRefWithEngine:engine name:@"purgeWritesCancelsTransactions"] childByAutoId];
  283. FIRDatabase *database = ref.database;
  284. NSMutableArray *events = [NSMutableArray array];
  285. [ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  286. [events addObject:[NSString stringWithFormat:@"value-%@", snapshot.value]];
  287. }];
  288. // Make sure the first value event is fired
  289. [self waitForRoundTrip:ref];
  290. [database goOffline];
  291. [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  292. [currentData setValue:@"1"];
  293. return [FIRTransactionResult successWithValue:currentData];
  294. } andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  295. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  296. [events addObject:@"cancel-1"];
  297. }];
  298. [ref runTransactionBlock:^FIRTransactionResult *(FIRMutableData *currentData) {
  299. [currentData setValue:@"2"];
  300. return [FIRTransactionResult successWithValue:currentData];
  301. } andCompletionBlock:^(NSError *error, BOOL committed, FIRDataSnapshot *snapshot) {
  302. XCTAssertEqual(error.code, kFErrorCodeWriteCanceled);
  303. [events addObject:@"cancel-2"];
  304. }];
  305. [database purgeOutstandingWrites];
  306. [self waitForEvents:ref];
  307. // The order should really be cancel-1 then cancel-2, but meh, to difficult to implement currently...
  308. XCTAssertEqualObjects(events, (@[@"value-<null>", @"value-1", @"value-2", @"value-<null>", @"cancel-2", @"cancel-1"]));
  309. }
  310. - (void) testPersistenceEnabled {
  311. id app = [[FIRFakeApp alloc] initWithName:@"testPersistenceEnabled" URL:self.databaseURL];
  312. FIRDatabase *database = [FIRDatabase databaseForApp:app];
  313. database.persistenceEnabled = YES;
  314. XCTAssertTrue(database.persistenceEnabled);
  315. // Just do a dummy observe that should get null added to the persistent cache.
  316. FIRDatabaseReference *ref = [[database reference] childByAutoId];
  317. [self waitForValueOf:ref toBe:[NSNull null]];
  318. // Now go offline and since null is cached offline, our observer should still complete.
  319. [database goOffline];
  320. [self waitForValueOf:ref toBe:[NSNull null]];
  321. }
  322. - (void) testPersistenceCacheSizeBytes {
  323. id app = [[FIRFakeApp alloc] initWithName:@"testPersistenceCacheSizeBytes" URL:self.databaseURL];
  324. FIRDatabase *database = [FIRDatabase databaseForApp:app];
  325. database.persistenceEnabled = YES;
  326. int oneMegabyte = 1 * 1024 * 1024;
  327. XCTAssertThrows([database setPersistenceCacheSizeBytes: 1], @"Cache must be a least 1 MB.");
  328. XCTAssertThrows([database setPersistenceCacheSizeBytes: 101 * oneMegabyte],
  329. @"Cache must be less than 100 MB.");
  330. database.persistenceCacheSizeBytes = 2 * oneMegabyte;
  331. XCTAssertEqual(2 * oneMegabyte, database.persistenceCacheSizeBytes);
  332. [database reference]; // Initialize database.
  333. XCTAssertThrows([database setPersistenceCacheSizeBytes: 3 * oneMegabyte],
  334. @"Persistence can't be changed after initialization.");
  335. XCTAssertEqual(2 * oneMegabyte, database.persistenceCacheSizeBytes);
  336. }
  337. - (void) testCallbackQueue {
  338. id app = [[FIRFakeApp alloc] initWithName:@"testCallbackQueue" URL:self.databaseURL];
  339. FIRDatabase *database = [FIRDatabase databaseForApp:app];
  340. dispatch_queue_t callbackQueue = dispatch_queue_create("testCallbackQueue", NULL);
  341. database.callbackQueue = callbackQueue;
  342. XCTAssertEqual(callbackQueue, database.callbackQueue);
  343. __block BOOL done = NO;
  344. [database.reference.childByAutoId observeSingleEventOfType:FIRDataEventTypeValue
  345. withBlock:^(FIRDataSnapshot *snapshot) {
  346. dispatch_assert_queue(callbackQueue);
  347. done = YES;
  348. }];
  349. WAIT_FOR(done);
  350. [database goOffline];
  351. }
  352. - (FIRDatabase *) defaultDatabase {
  353. return [self databaseForURL:self.databaseURL];
  354. }
  355. - (FIRDatabase *) databaseForURL:(NSString *)url {
  356. NSString *name = [NSString stringWithFormat:@"url:%@", url];
  357. return [self databaseForURL:url name:name];
  358. }
  359. - (FIRDatabase *) databaseForURL:(NSString *)url name:(NSString *)name {
  360. id app = [[FIRFakeApp alloc] initWithName:name URL:url];
  361. return [FIRDatabase databaseForApp:app];
  362. }
  363. @end