FIRDatabaseTests.m 16 KB

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