RLMObjectServerTests.mm 115 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMSyncTestCase.h"
  19. #import "RLMTestUtils.h"
  20. #import "RLMSyncSessionRefreshHandle+ObjectServerTests.h"
  21. #import "RLMSyncUser+ObjectServerTests.h"
  22. #import "RLMRealm+Sync.h"
  23. #import "RLMRealmConfiguration_Private.h"
  24. #import "RLMRealmUtil.hpp"
  25. #import "RLMRealm_Dynamic.h"
  26. #import "RLMRealm_Private.hpp"
  27. #import "RLMSyncUtil_Private.h"
  28. #import "shared_realm.hpp"
  29. #pragma mark - Test objects
  30. @interface PartialSyncObjectA : RLMObject
  31. @property NSInteger number;
  32. @property NSString *string;
  33. + (instancetype)objectWithNumber:(NSInteger)number string:(NSString *)string;
  34. @end
  35. @interface PartialSyncObjectB : RLMObject
  36. @property NSInteger number;
  37. @property NSString *firstString;
  38. @property NSString *secondString;
  39. + (instancetype)objectWithNumber:(NSInteger)number firstString:(NSString *)first secondString:(NSString *)second;
  40. @end
  41. @implementation PartialSyncObjectA
  42. + (instancetype)objectWithNumber:(NSInteger)number string:(NSString *)string {
  43. PartialSyncObjectA *object = [[PartialSyncObjectA alloc] init];
  44. object.number = number;
  45. object.string = string;
  46. return object;
  47. }
  48. @end
  49. @implementation PartialSyncObjectB
  50. + (instancetype)objectWithNumber:(NSInteger)number firstString:(NSString *)first secondString:(NSString *)second {
  51. PartialSyncObjectB *object = [[PartialSyncObjectB alloc] init];
  52. object.number = number;
  53. object.firstString = first;
  54. object.secondString = second;
  55. return object;
  56. }
  57. @end
  58. @interface RLMObjectServerTests : RLMSyncTestCase
  59. @end
  60. @implementation RLMObjectServerTests
  61. #pragma mark - Authentication and Tokens
  62. /// Valid username/password credentials should be able to log in a user. Using the same credentials should return the
  63. /// same user object.
  64. - (void)testUsernamePasswordAuthentication {
  65. RLMSyncUser *firstUser = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  66. register:YES]
  67. server:[RLMSyncTestCase authServerURL]];
  68. RLMSyncUser *secondUser = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  69. register:NO]
  70. server:[RLMSyncTestCase authServerURL]];
  71. // Two users created with the same credential should resolve to the same actual user.
  72. XCTAssertTrue([firstUser.identity isEqualToString:secondUser.identity]);
  73. // Authentication server property should be properly set.
  74. XCTAssertEqualObjects(firstUser.authenticationServer, [RLMSyncTestCase authServerURL]);
  75. XCTAssertFalse(firstUser.isAdmin);
  76. }
  77. /// A valid admin token should be able to log in a user.
  78. - (void)testAdminTokenAuthentication {
  79. RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithAccessToken:self.adminToken identity:@"test"];
  80. XCTAssertNotNil(credentials);
  81. RLMSyncUser *user = [self logInUserForCredentials:credentials server:[RLMObjectServerTests authServerURL]];
  82. XCTAssertTrue(user.isAdmin);
  83. }
  84. /// An invalid username/password credential should not be able to log in a user and a corresponding error should be generated.
  85. - (void)testInvalidPasswordAuthentication {
  86. [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd) register:YES]
  87. server:[RLMSyncTestCase authServerURL]];
  88. RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithUsername:NSStringFromSelector(_cmd)
  89. password:@"INVALID_PASSWORD"
  90. register:NO];
  91. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  92. [RLMSyncUser logInWithCredentials:credentials
  93. authServerURL:[RLMObjectServerTests authServerURL]
  94. onCompletion:^(RLMSyncUser *user, NSError *error) {
  95. XCTAssertNil(user);
  96. XCTAssertNotNil(error);
  97. XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
  98. XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
  99. XCTAssertNotNil(error.localizedDescription);
  100. [expectation fulfill];
  101. }];
  102. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  103. }
  104. /// A non-existsing user should not be able to log in and a corresponding error should be generated.
  105. - (void)testNonExistingUsernameAuthentication {
  106. RLMSyncCredentials *credentials = [RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  107. register:NO];
  108. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  109. [RLMSyncUser logInWithCredentials:credentials
  110. authServerURL:[RLMObjectServerTests authServerURL]
  111. onCompletion:^(RLMSyncUser *user, NSError *error) {
  112. XCTAssertNil(user);
  113. XCTAssertNotNil(error);
  114. XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
  115. XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
  116. XCTAssertNotNil(error.localizedDescription);
  117. [expectation fulfill];
  118. }];
  119. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  120. }
  121. /// Registering a user with existing username should return corresponding error.
  122. - (void)testExistingUsernameRegistration {
  123. RLMSyncCredentials *credentials = [RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  124. register:YES];
  125. [self logInUserForCredentials:credentials server:[RLMSyncTestCase authServerURL]];
  126. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  127. [RLMSyncUser logInWithCredentials:credentials
  128. authServerURL:[RLMObjectServerTests authServerURL]
  129. onCompletion:^(RLMSyncUser *user, NSError *error) {
  130. XCTAssertNil(user);
  131. XCTAssertNotNil(error);
  132. XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
  133. XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
  134. XCTAssertNotNil(error.localizedDescription);
  135. [expectation fulfill];
  136. }];
  137. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  138. }
  139. /// Errors reported in RLMSyncManager.errorHandler shouldn't contain sync error domain errors as underlying error
  140. - (void)testSyncErrorHandlerErrorDomain {
  141. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  142. register:YES]
  143. server:[RLMObjectServerTests authServerURL]];
  144. XCTAssertNotNil(user);
  145. NSURL *realmURL = [NSURL URLWithString:@"realm://127.0.0.1:9080/THE_PATH_USER_DONT_HAVE_ACCESS_TO/test"];
  146. RLMRealmConfiguration *c = [user configurationWithURL:realmURL fullSynchronization:true];
  147. NSError *error = nil;
  148. __attribute__((objc_precise_lifetime)) RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:&error];
  149. XCTAssertNil(error);
  150. XCTAssertTrue(realm.isEmpty);
  151. XCTestExpectation *expectation = [self expectationWithDescription:@""];
  152. [RLMSyncManager sharedManager].errorHandler = ^(__unused NSError *error,
  153. __unused RLMSyncSession *session) {
  154. XCTAssertTrue([error.domain isEqualToString:RLMSyncErrorDomain]);
  155. XCTAssertFalse([[error.userInfo[kRLMSyncUnderlyingErrorKey] domain] isEqualToString:RLMSyncErrorDomain]);
  156. [expectation fulfill];
  157. };
  158. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  159. }
  160. /// The pre-emptive token refresh subsystem should function, and properly refresh the token.
  161. - (void)testPreemptiveTokenRefresh {
  162. // Prepare the test.
  163. __block NSInteger refreshCount = 0;
  164. __block NSInteger errorCount = 0;
  165. [RLMSyncManager sharedManager].errorHandler = ^(__unused NSError *error,
  166. __unused RLMSyncSession *session) {
  167. errorCount++;
  168. };
  169. __block XCTestExpectation *ex;
  170. [RLMSyncSessionRefreshHandle calculateFireDateUsingTestLogic:YES
  171. blockOnRefreshCompletion:^(BOOL success) {
  172. XCTAssertTrue(success);
  173. refreshCount++;
  174. [ex fulfill];
  175. }];
  176. // Open the Realm.
  177. NSURL *url = REALM_URL();
  178. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  179. register:true]
  180. server:[RLMObjectServerTests authServerURL]];
  181. __attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:user];
  182. ex = [self expectationWithDescription:@"Timer fired"];
  183. [self waitForExpectationsWithTimeout:10 handler:nil];
  184. XCTAssertTrue(errorCount == 0);
  185. XCTAssertTrue(refreshCount > 0);
  186. }
  187. #pragma mark - Users
  188. /// `[RLMSyncUser all]` should be updated once a user is logged in.
  189. - (void)testBasicUserPersistence {
  190. XCTAssertNil([RLMSyncUser currentUser]);
  191. XCTAssertEqual([[RLMSyncUser allUsers] count], 0U);
  192. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  193. register:YES]
  194. server:[RLMObjectServerTests authServerURL]];
  195. XCTAssertNotNil(user);
  196. XCTAssertEqual([[RLMSyncUser allUsers] count], 1U);
  197. XCTAssertEqualObjects([RLMSyncUser allUsers], @{user.identity: user});
  198. XCTAssertEqualObjects([RLMSyncUser currentUser], user);
  199. RLMSyncUser *user2 = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:[NSStringFromSelector(_cmd) stringByAppendingString:@"2"]
  200. register:YES]
  201. server:[RLMObjectServerTests authServerURL]];
  202. XCTAssertEqual([[RLMSyncUser allUsers] count], 2U);
  203. NSDictionary *dict2 = @{user.identity: user, user2.identity: user2};
  204. XCTAssertEqualObjects([RLMSyncUser allUsers], dict2);
  205. RLMAssertThrowsWithReasonMatching([RLMSyncUser currentUser], @"currentUser cannot be called if more that one valid, logged-in user exists");
  206. }
  207. /// `[RLMSyncUser currentUser]` should become nil if the user is logged out.
  208. - (void)testCurrentUserLogout {
  209. XCTAssertNil([RLMSyncUser currentUser]);
  210. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  211. register:YES]
  212. server:[RLMObjectServerTests authServerURL]];
  213. XCTAssertNotNil(user);
  214. XCTAssertEqualObjects([RLMSyncUser currentUser], user);
  215. [user logOut];
  216. XCTAssertNil([RLMSyncUser currentUser]);
  217. }
  218. /// A sync user should return a session when asked for it based on the path.
  219. - (void)testUserGetSessionForValidURL {
  220. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  221. register:YES]
  222. server:[RLMObjectServerTests authServerURL]];
  223. NSURL *url = REALM_URL();
  224. [self openRealmForURL:url user:user immediatelyBlock:^{
  225. RLMSyncSession *session = [user sessionForURL:url];
  226. XCTAssertNotNil(session);
  227. }];
  228. // Check session existence after binding.
  229. RLMSyncSession *session = [user sessionForURL:url];
  230. XCTAssertNotNil(session);
  231. }
  232. /// A sync user should return nil when asked for a URL that doesn't exist.
  233. - (void)testUserGetSessionForInvalidURL {
  234. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  235. register:YES]
  236. server:[RLMObjectServerTests authServerURL]];
  237. RLMSyncSession *badSession = [user sessionForURL:[NSURL URLWithString:@"realm://127.0.0.1:9080/noSuchRealm"]];
  238. XCTAssertNil(badSession);
  239. }
  240. /// A sync user should be able to successfully change their own password.
  241. - (void)testUserChangePassword {
  242. NSString *userName = NSStringFromSelector(_cmd);
  243. NSString *firstPassword = @"a";
  244. NSString *secondPassword = @"b";
  245. // Successfully create user, change its password, log out,
  246. // then fail to change password again due to being logged out.
  247. {
  248. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:firstPassword
  249. register:YES];
  250. RLMSyncUser *user = [self logInUserForCredentials:creds
  251. server:[RLMObjectServerTests authServerURL]];
  252. XCTestExpectation *ex = [self expectationWithDescription:@"change password callback invoked"];
  253. [user changePassword:secondPassword completion:^(NSError * _Nullable error) {
  254. XCTAssertNil(error);
  255. [ex fulfill];
  256. }];
  257. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  258. [user logOut];
  259. ex = [self expectationWithDescription:@"change password callback invoked"];
  260. [user changePassword:@"fail" completion:^(NSError * _Nullable error) {
  261. XCTAssertNotNil(error);
  262. [ex fulfill];
  263. }];
  264. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  265. }
  266. // Fail to log in with original password.
  267. {
  268. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:firstPassword
  269. register:NO];
  270. XCTestExpectation *ex = [self expectationWithDescription:@"login callback invoked"];
  271. [RLMSyncUser logInWithCredentials:creds
  272. authServerURL:[RLMObjectServerTests authServerURL]
  273. onCompletion:^(RLMSyncUser *user, NSError *error) {
  274. XCTAssertNil(user);
  275. XCTAssertNotNil(error);
  276. XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
  277. XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
  278. XCTAssertNotNil(error.localizedDescription);
  279. [ex fulfill];
  280. }];
  281. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  282. }
  283. // Successfully log in with new password.
  284. {
  285. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:secondPassword
  286. register:NO];
  287. RLMSyncUser *user = [self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]];
  288. XCTAssertNotNil(user);
  289. XCTAssertEqualObjects(RLMSyncUser.currentUser, user);
  290. [user logOut];
  291. XCTAssertNil(RLMSyncUser.currentUser);
  292. }
  293. }
  294. /// A sync admin user should be able to successfully change another user's password.
  295. - (void)testOtherUserChangePassword {
  296. // Create admin user.
  297. NSURL *url = [RLMObjectServerTests authServerURL];
  298. RLMSyncUser *adminUser = [self createAdminUserForURL:url username:[[NSUUID UUID] UUIDString]];
  299. NSString *username = NSStringFromSelector(_cmd);
  300. NSString *firstPassword = @"a";
  301. NSString *secondPassword = @"b";
  302. NSString *nonAdminUserID = nil;
  303. // Successfully create user.
  304. {
  305. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:username
  306. password:firstPassword
  307. register:YES];
  308. RLMSyncUser *user = [self logInUserForCredentials:creds server:url];
  309. nonAdminUserID = user.identity;
  310. [user logOut];
  311. }
  312. // Fail to change password from non-admin user.
  313. {
  314. NSString *username2 = [NSString stringWithFormat:@"%@_2", username];
  315. RLMSyncCredentials *creds2 = [RLMSyncCredentials credentialsWithUsername:username2
  316. password:@"a"
  317. register:YES];
  318. RLMSyncUser *user2 = [self logInUserForCredentials:creds2 server:url];
  319. XCTestExpectation *ex = [self expectationWithDescription:@"change password callback invoked"];
  320. [user2 changePassword:@"foobar" forUserID:nonAdminUserID completion:^(NSError *error) {
  321. XCTAssertNotNil(error);
  322. [ex fulfill];
  323. }];
  324. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  325. }
  326. // Change password from admin user.
  327. {
  328. XCTestExpectation *ex = [self expectationWithDescription:@"change password callback invoked"];
  329. [adminUser changePassword:secondPassword forUserID:nonAdminUserID completion:^(NSError *error) {
  330. XCTAssertNil(error);
  331. [ex fulfill];
  332. }];
  333. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  334. }
  335. // Fail to log in with original password.
  336. {
  337. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:username
  338. password:firstPassword
  339. register:NO];
  340. XCTestExpectation *ex = [self expectationWithDescription:@"login callback invoked"];
  341. [RLMSyncUser logInWithCredentials:creds
  342. authServerURL:[RLMObjectServerTests authServerURL]
  343. onCompletion:^(RLMSyncUser *user, NSError *error) {
  344. XCTAssertNil(user);
  345. XCTAssertNotNil(error);
  346. XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
  347. XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
  348. XCTAssertNotNil(error.localizedDescription);
  349. [ex fulfill];
  350. }];
  351. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  352. }
  353. // Successfully log in with new password.
  354. {
  355. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:username
  356. password:secondPassword
  357. register:NO];
  358. RLMSyncUser *user = [self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]];
  359. XCTAssertNotNil(user);
  360. [user logOut];
  361. }
  362. }
  363. - (void)testRequestPasswordResetForRegisteredUser {
  364. NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
  365. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:@"a" register:YES];
  366. [[self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]] logOut];
  367. XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
  368. [RLMSyncUser requestPasswordResetForAuthServer:[RLMObjectServerTests authServerURL] userEmail:userName completion:^(NSError *error) {
  369. XCTAssertNil(error);
  370. [ex fulfill];
  371. }];
  372. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  373. NSString *token = [self emailForAddress:userName];
  374. XCTAssertNotNil(token);
  375. // Use the password reset token
  376. ex = [self expectationWithDescription:@"callback invoked"];
  377. [RLMSyncUser completePasswordResetForAuthServer:[RLMObjectServerTests authServerURL] token:token password:@"new password"
  378. completion:^(NSError *error) {
  379. XCTAssertNil(error);
  380. [ex fulfill];
  381. }];
  382. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  383. // Should now be able to log in with the new password
  384. {
  385. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName
  386. password:@"new password"
  387. register:NO];
  388. RLMSyncUser *user = [self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]];
  389. XCTAssertNotNil(user);
  390. [user logOut];
  391. }
  392. // Reusing the token should fail
  393. ex = [self expectationWithDescription:@"callback invoked"];
  394. [RLMSyncUser completePasswordResetForAuthServer:[RLMObjectServerTests authServerURL] token:token password:@"new password 2"
  395. completion:^(NSError *error) {
  396. XCTAssertNotNil(error);
  397. [ex fulfill];
  398. }];
  399. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  400. }
  401. - (void)testRequestPasswordResetForNonexistentUser {
  402. NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
  403. XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
  404. [RLMSyncUser requestPasswordResetForAuthServer:[RLMObjectServerTests authServerURL] userEmail:userName completion:^(NSError *error) {
  405. // Not an error even though the user doesn't exist
  406. XCTAssertNil(error);
  407. [ex fulfill];
  408. }];
  409. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  410. // Should not have sent an email to the non-registered user
  411. XCTAssertNil([self emailForAddress:userName]);
  412. }
  413. - (void)testRequestPasswordResetWithBadAuthURL {
  414. NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
  415. XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
  416. NSURL *badAuthUrl = [[RLMObjectServerTests authServerURL] URLByAppendingPathComponent:@"/bad"];
  417. [RLMSyncUser requestPasswordResetForAuthServer:badAuthUrl userEmail:userName completion:^(NSError *error) {
  418. XCTAssertNotNil(error);
  419. XCTAssertEqualObjects(error.userInfo[@"statusCode"], @404);
  420. [ex fulfill];
  421. }];
  422. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  423. }
  424. - (void)testRequestConfirmEmailForRegisteredUser {
  425. NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
  426. RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:@"a" register:YES];
  427. [[self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]] logOut];
  428. // This token is sent by ROS upon user registration
  429. NSString *registrationToken = [self emailForAddress:userName];
  430. XCTAssertNotNil(registrationToken);
  431. XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
  432. [RLMSyncUser requestEmailConfirmationForAuthServer:[RLMObjectServerTests authServerURL]
  433. userEmail:userName completion:^(NSError *error) {
  434. XCTAssertNil(error);
  435. [ex fulfill];
  436. }];
  437. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  438. // This token should have been created when requestEmailConfirmationForAuthServer was called
  439. NSString *token = [self emailForAddress:userName];
  440. XCTAssertNotNil(token);
  441. XCTAssertNotEqual(token, registrationToken);
  442. // Use the token
  443. ex = [self expectationWithDescription:@"callback invoked"];
  444. [RLMSyncUser confirmEmailForAuthServer:[RLMObjectServerTests authServerURL] token:token
  445. completion:^(NSError *error) {
  446. XCTAssertNil(error);
  447. [ex fulfill];
  448. }];
  449. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  450. // Reusing the token should fail
  451. ex = [self expectationWithDescription:@"callback invoked"];
  452. [RLMSyncUser confirmEmailForAuthServer:[RLMObjectServerTests authServerURL] token:token
  453. completion:^(NSError *error) {
  454. XCTAssertNotNil(error);
  455. [ex fulfill];
  456. }];
  457. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  458. }
  459. - (void)testRequestConfirmEmailForNonexistentUser {
  460. NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
  461. XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
  462. [RLMSyncUser requestEmailConfirmationForAuthServer:[RLMObjectServerTests authServerURL]
  463. userEmail:userName completion:^(NSError *error) {
  464. // Not an error even though the user doesn't exist
  465. XCTAssertNil(error);
  466. [ex fulfill];
  467. }];
  468. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  469. // Should not have sent an email to the non-registered user
  470. XCTAssertNil([self emailForAddress:userName]);
  471. }
  472. /// A sync admin user should be able to retrieve information about other users.
  473. - (void)testRetrieveUserInfo {
  474. NSString *nonAdminUsername = @"meela@realm.example.org";
  475. NSString *adminUsername = @"jyaku";
  476. NSString *pw = @"p";
  477. NSURL *server = [RLMObjectServerTests authServerURL];
  478. // Create a non-admin user.
  479. RLMSyncCredentials *c1 = [RLMSyncCredentials credentialsWithUsername:nonAdminUsername password:pw register:YES];
  480. RLMSyncUser *nonAdminUser = [self logInUserForCredentials:c1 server:server];
  481. // Create an admin user.
  482. __unused RLMSyncUser *adminUser = [self createAdminUserForURL:server username:adminUsername];
  483. // Create another admin user.
  484. RLMSyncUser *userDoingLookups = [self createAdminUserForURL:server username:[[NSUUID UUID] UUIDString]];
  485. // Get the non-admin user's info.
  486. XCTestExpectation *ex1 = [self expectationWithDescription:@"should be able to get info about non-admin user"];
  487. [userDoingLookups retrieveInfoForUser:nonAdminUsername
  488. identityProvider:RLMIdentityProviderUsernamePassword
  489. completion:^(RLMSyncUserInfo *info, NSError *err) {
  490. XCTAssertNil(err);
  491. XCTAssertNotNil(info);
  492. XCTAssertGreaterThan([info.accounts count], ((NSUInteger) 0));
  493. RLMSyncUserAccountInfo *acctInfo = [info.accounts firstObject];
  494. XCTAssertEqualObjects(acctInfo.providerUserIdentity, nonAdminUsername);
  495. XCTAssertEqualObjects(acctInfo.provider, RLMIdentityProviderUsernamePassword);
  496. XCTAssertFalse(info.isAdmin);
  497. [ex1 fulfill];
  498. }];
  499. [self waitForExpectationsWithTimeout:10 handler:nil];
  500. // Get the admin user's info.
  501. XCTestExpectation *ex2 = [self expectationWithDescription:@"should be able to get info about admin user"];
  502. [userDoingLookups retrieveInfoForUser:adminUsername
  503. identityProvider:RLMIdentityProviderDebug
  504. completion:^(RLMSyncUserInfo *info, NSError *err) {
  505. XCTAssertNil(err);
  506. XCTAssertNotNil(info);
  507. XCTAssertGreaterThan([info.accounts count], ((NSUInteger) 0));
  508. RLMSyncUserAccountInfo *acctInfo = [info.accounts firstObject];
  509. XCTAssertEqualObjects(acctInfo.providerUserIdentity, adminUsername);
  510. XCTAssertEqualObjects(acctInfo.provider, RLMIdentityProviderDebug);
  511. XCTAssertTrue(info.isAdmin);
  512. [ex2 fulfill];
  513. }];
  514. [self waitForExpectationsWithTimeout:10 handler:nil];
  515. // Get invalid user's info.
  516. XCTestExpectation *ex3 = [self expectationWithDescription:@"should fail for non-existent user"];
  517. [userDoingLookups retrieveInfoForUser:@"invalid_user@realm.example.org"
  518. identityProvider:RLMIdentityProviderUsernamePassword
  519. completion:^(RLMSyncUserInfo *info, NSError *err) {
  520. XCTAssertNotNil(err);
  521. XCTAssertEqualObjects(err.domain, RLMSyncAuthErrorDomain);
  522. XCTAssertEqual(err.code, RLMSyncAuthErrorUserDoesNotExist);
  523. XCTAssertNil(info);
  524. [ex3 fulfill];
  525. }];
  526. [self waitForExpectationsWithTimeout:10 handler:nil];
  527. // Get info using user without admin privileges.
  528. XCTestExpectation *ex4 = [self expectationWithDescription:@"should fail for user without admin privileges"];
  529. [nonAdminUser retrieveInfoForUser:adminUsername
  530. identityProvider:RLMIdentityProviderUsernamePassword
  531. completion:^(RLMSyncUserInfo *info, NSError *err) {
  532. XCTAssertNotNil(err);
  533. XCTAssertEqualObjects(err.domain, RLMSyncAuthErrorDomain);
  534. // FIXME: Shouldn't this be RLMSyncAuthErrorAccessDeniedOrInvalidPath?
  535. XCTAssertEqual(err.code, RLMSyncAuthErrorUserDoesNotExist);
  536. XCTAssertNil(info);
  537. [ex4 fulfill];
  538. }];
  539. [self waitForExpectationsWithTimeout:10 handler:nil];
  540. }
  541. /// The login queue argument should be respected.
  542. - (void)testLoginQueueForSuccessfulLogin {
  543. // Make global queue
  544. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  545. RLMSyncCredentials *c1 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
  546. password:@"p"
  547. register:YES];
  548. XCTestExpectation *ex1 = [self expectationWithDescription:@"User logs in successfully on background queue"];
  549. [RLMSyncUser logInWithCredentials:c1
  550. authServerURL:[RLMObjectServerTests authServerURL]
  551. timeout:30.0
  552. callbackQueue:queue
  553. onCompletion:^(RLMSyncUser *user, __unused NSError *error) {
  554. XCTAssertNotNil(user);
  555. XCTAssertFalse([NSThread isMainThread]);
  556. [ex1 fulfill];
  557. }];
  558. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  559. RLMSyncCredentials *c2 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
  560. password:@"p"
  561. register:YES];
  562. XCTestExpectation *ex2 = [self expectationWithDescription:@"User logs in successfully on main queue"];
  563. [RLMSyncUser logInWithCredentials:c2
  564. authServerURL:[RLMObjectServerTests authServerURL]
  565. timeout:30.0
  566. callbackQueue:dispatch_get_main_queue()
  567. onCompletion:^(RLMSyncUser *user, __unused NSError *error) {
  568. XCTAssertNotNil(user);
  569. XCTAssertTrue([NSThread isMainThread]);
  570. [ex2 fulfill];
  571. }];
  572. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  573. }
  574. /// The login queue argument should be respected.
  575. - (void)testLoginQueueForFailedLogin {
  576. // Make global queue
  577. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  578. RLMSyncCredentials *c1 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
  579. password:@"p"
  580. register:NO];
  581. XCTestExpectation *ex1 = [self expectationWithDescription:@"Error returned on background queue"];
  582. [RLMSyncUser logInWithCredentials:c1
  583. authServerURL:[RLMObjectServerTests authServerURL]
  584. timeout:30.0
  585. callbackQueue:queue
  586. onCompletion:^(__unused RLMSyncUser *user, NSError *error) {
  587. XCTAssertNotNil(error);
  588. XCTAssertFalse([NSThread isMainThread]);
  589. [ex1 fulfill];
  590. }];
  591. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  592. RLMSyncCredentials *c2 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
  593. password:@"p"
  594. register:NO];
  595. XCTestExpectation *ex2 = [self expectationWithDescription:@"Error returned on main queue"];
  596. [RLMSyncUser logInWithCredentials:c2
  597. authServerURL:[RLMObjectServerTests authServerURL]
  598. timeout:30.0
  599. callbackQueue:dispatch_get_main_queue()
  600. onCompletion:^(__unused RLMSyncUser *user, NSError *error) {
  601. XCTAssertNotNil(error);
  602. XCTAssertTrue([NSThread isMainThread]);
  603. [ex2 fulfill];
  604. }];
  605. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  606. }
  607. - (void)testUserExpirationCallback {
  608. NSString *username = NSStringFromSelector(_cmd);
  609. RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithUsername:username
  610. password:@"a"
  611. register:YES];
  612. RLMSyncUser *user = [self logInUserForCredentials:credentials
  613. server:[RLMObjectServerTests authServerURL]];
  614. XCTestExpectation *ex = [self expectationWithDescription:@"callback should fire"];
  615. // Set a callback on the user
  616. __weak RLMSyncUser *weakUser = user;
  617. user.errorHandler = ^(RLMSyncUser *u, NSError *error) {
  618. XCTAssertEqualObjects(u.identity, weakUser.identity);
  619. // Make sure we get the right error.
  620. XCTAssertEqualObjects(error.domain, RLMSyncAuthErrorDomain);
  621. XCTAssertEqual(error.code, RLMSyncAuthErrorAccessDeniedOrInvalidPath);
  622. [ex fulfill];
  623. };
  624. // Screw up the token on the user using a debug API
  625. [self manuallySetRefreshTokenForUser:user value:@"not_a_real_refresh_token"];
  626. // Try to log in a Realm; this will cause our errorHandler block defined above to be fired.
  627. __attribute__((objc_precise_lifetime)) RLMRealm *r = [self immediatelyOpenRealmForURL:REALM_URL() user:user];
  628. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  629. XCTAssertTrue(user.state == RLMSyncUserStateLoggedOut);
  630. }
  631. #pragma mark - Basic Sync
  632. /// It should be possible to successfully open a Realm configured for sync with an access token.
  633. - (void)testOpenRealmWithAdminToken {
  634. // FIXME (tests): opening a Realm with the access token, then opening a Realm at the same virtual path
  635. // with normal credentials, causes Realms to fail to bind with a "bad virtual path" error.
  636. RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithAccessToken:self.adminToken identity:@"test"];
  637. XCTAssertNotNil(credentials);
  638. RLMSyncUser *user = [self logInUserForCredentials:credentials
  639. server:[RLMObjectServerTests authServerURL]];
  640. NSURL *url = [NSURL URLWithString:@"realm://127.0.0.1:9080/testSyncWithAdminToken"];
  641. RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:YES];
  642. NSError *error = nil;
  643. RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:&error];
  644. XCTAssertNil(error);
  645. XCTAssertTrue(realm.isEmpty);
  646. }
  647. /// It should be possible to successfully open a Realm configured for sync with a normal user.
  648. - (void)testOpenRealmWithNormalCredentials {
  649. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  650. register:YES]
  651. server:[RLMObjectServerTests authServerURL]];
  652. NSURL *url = REALM_URL();
  653. RLMRealm *realm = [self openRealmForURL:url user:user];
  654. XCTAssertTrue(realm.isEmpty);
  655. }
  656. /// If client B adds objects to a synced Realm, client A should see those objects.
  657. - (void)testAddObjects {
  658. NSURL *url = REALM_URL();
  659. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  660. register:self.isParent]
  661. server:[RLMObjectServerTests authServerURL]];
  662. RLMRealm *realm = [self openRealmForURL:url user:user];
  663. if (self.isParent) {
  664. CHECK_COUNT(0, SyncObject, realm);
  665. RLMRunChildAndWait();
  666. [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@3]];
  667. } else {
  668. // Add objects.
  669. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  670. [self waitForUploadsForRealm:realm];
  671. CHECK_COUNT(3, SyncObject, realm);
  672. }
  673. }
  674. /// If client B deletes objects from a synced Realm, client A should see the effects of that deletion.
  675. - (void)testDeleteObjects {
  676. NSURL *url = REALM_URL();
  677. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  678. register:self.isParent]
  679. server:[RLMObjectServerTests authServerURL]];
  680. RLMRealm *realm = [self openRealmForURL:url user:user];
  681. if (self.isParent) {
  682. // Add objects.
  683. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1", @"parent-2", @"parent-3"]];
  684. [self waitForUploadsForRealm:realm];
  685. CHECK_COUNT(3, SyncObject, realm);
  686. RLMRunChildAndWait();
  687. [self waitForDownloadsForRealm:realm];
  688. CHECK_COUNT(0, SyncObject, realm);
  689. } else {
  690. [self waitForDownloadsForRealm:realm];
  691. CHECK_COUNT(3, SyncObject, realm);
  692. [realm beginWriteTransaction];
  693. [realm deleteAllObjects];
  694. [realm commitWriteTransaction];
  695. [self waitForUploadsForRealm:realm];
  696. CHECK_COUNT(0, SyncObject, realm);
  697. }
  698. }
  699. #pragma mark - Encryption
  700. /// If client B encrypts its synced Realm, client A should be able to access that Realm with a different encryption key.
  701. - (void)testEncryptedSyncedRealm {
  702. NSURL *url = REALM_URL();
  703. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  704. register:self.isParent]
  705. server:[RLMObjectServerTests authServerURL]];
  706. NSData *key = RLMGenerateKey();
  707. RLMRealm *realm = [self openRealmForURL:url user:user encryptionKey:key
  708. stopPolicy:RLMSyncStopPolicyAfterChangesUploaded immediatelyBlock:nil];
  709. if (self.isParent) {
  710. CHECK_COUNT(0, SyncObject, realm);
  711. RLMRunChildAndWait();
  712. [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@3]];
  713. } else {
  714. // Add objects.
  715. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  716. [self waitForUploadsForRealm:realm];
  717. CHECK_COUNT(3, SyncObject, realm);
  718. }
  719. }
  720. /// If an encrypted synced Realm is re-opened with the wrong key, throw an exception.
  721. - (void)testEncryptedSyncedRealmWrongKey {
  722. NSURL *url = REALM_URL();
  723. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  724. register:self.isParent]
  725. server:[RLMObjectServerTests authServerURL]];
  726. if (self.isParent) {
  727. NSString *path;
  728. @autoreleasepool {
  729. RLMRealm *realm = [self openRealmForURL:url user:user encryptionKey:RLMGenerateKey()
  730. stopPolicy:RLMSyncStopPolicyImmediately immediatelyBlock:nil];
  731. path = realm.configuration.pathOnDisk;
  732. CHECK_COUNT(0, SyncObject, realm);
  733. RLMRunChildAndWait();
  734. [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@3]];
  735. }
  736. RLMRealmConfiguration *c = [RLMRealmConfiguration defaultConfiguration];
  737. c.fileURL = [NSURL fileURLWithPath:path];
  738. RLMAssertThrowsWithError([RLMRealm realmWithConfiguration:c error:nil],
  739. @"Unable to open a realm at path",
  740. RLMErrorFileAccess,
  741. @"invalid mnemonic");
  742. c.encryptionKey = RLMGenerateKey();
  743. RLMAssertThrowsWithError([RLMRealm realmWithConfiguration:c error:nil],
  744. @"Unable to open a realm at path",
  745. RLMErrorFileAccess,
  746. @"Realm file decryption failed");
  747. } else {
  748. RLMRealm *realm = [self openRealmForURL:url user:user encryptionKey:RLMGenerateKey()
  749. stopPolicy:RLMSyncStopPolicyImmediately immediatelyBlock:nil];
  750. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  751. [self waitForUploadsForRealm:realm];
  752. CHECK_COUNT(3, SyncObject, realm);
  753. }
  754. }
  755. #pragma mark - Multiple Realm Sync
  756. /// If a client opens multiple Realms, there should be one session object for each Realm that was opened.
  757. - (void)testMultipleRealmsSessions {
  758. NSURL *urlA = CUSTOM_REALM_URL(@"a");
  759. NSURL *urlB = CUSTOM_REALM_URL(@"b");
  760. NSURL *urlC = CUSTOM_REALM_URL(@"c");
  761. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  762. register:self.isParent]
  763. server:[RLMObjectServerTests authServerURL]];
  764. // Open three Realms.
  765. __attribute__((objc_precise_lifetime)) RLMRealm *realmealmA = [self openRealmForURL:urlA user:user];
  766. __attribute__((objc_precise_lifetime)) RLMRealm *realmealmB = [self openRealmForURL:urlB user:user];
  767. __attribute__((objc_precise_lifetime)) RLMRealm *realmealmC = [self openRealmForURL:urlC user:user];
  768. // Make sure there are three active sessions for the user.
  769. XCTAssert(user.allSessions.count == 3, @"Expected 3 sessions, but didn't get 3 sessions");
  770. XCTAssertNotNil([user sessionForURL:urlA], @"Expected to get a session for URL A");
  771. XCTAssertNotNil([user sessionForURL:urlB], @"Expected to get a session for URL B");
  772. XCTAssertNotNil([user sessionForURL:urlC], @"Expected to get a session for URL C");
  773. XCTAssertTrue([user sessionForURL:urlA].state == RLMSyncSessionStateActive, @"Expected active session for URL A");
  774. XCTAssertTrue([user sessionForURL:urlB].state == RLMSyncSessionStateActive, @"Expected active session for URL B");
  775. XCTAssertTrue([user sessionForURL:urlC].state == RLMSyncSessionStateActive, @"Expected active session for URL C");
  776. }
  777. /// A client should be able to open multiple Realms and add objects to each of them.
  778. - (void)testMultipleRealmsAddObjects {
  779. NSURL *urlA = CUSTOM_REALM_URL(@"a");
  780. NSURL *urlB = CUSTOM_REALM_URL(@"b");
  781. NSURL *urlC = CUSTOM_REALM_URL(@"c");
  782. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  783. register:self.isParent]
  784. server:[RLMObjectServerTests authServerURL]];
  785. RLMRealm *realmA = [self openRealmForURL:urlA user:user];
  786. RLMRealm *realmB = [self openRealmForURL:urlB user:user];
  787. RLMRealm *realmC = [self openRealmForURL:urlC user:user];
  788. if (self.isParent) {
  789. [self waitForDownloadsForRealm:realmA];
  790. [self waitForDownloadsForRealm:realmB];
  791. [self waitForDownloadsForRealm:realmC];
  792. CHECK_COUNT(0, SyncObject, realmA);
  793. CHECK_COUNT(0, SyncObject, realmB);
  794. CHECK_COUNT(0, SyncObject, realmC);
  795. RLMRunChildAndWait();
  796. [self waitForDownloadsForUser:user
  797. realms:@[realmA, realmB, realmC]
  798. realmURLs:@[urlA, urlB, urlC]
  799. expectedCounts:@[@3, @2, @5]];
  800. } else {
  801. // Add objects.
  802. [self addSyncObjectsToRealm:realmA
  803. descriptions:@[@"child-A1", @"child-A2", @"child-A3"]];
  804. [self addSyncObjectsToRealm:realmB
  805. descriptions:@[@"child-B1", @"child-B2"]];
  806. [self addSyncObjectsToRealm:realmC
  807. descriptions:@[@"child-C1", @"child-C2", @"child-C3", @"child-C4", @"child-C5"]];
  808. [self waitForUploadsForRealm:realmA];
  809. [self waitForUploadsForRealm:realmB];
  810. [self waitForUploadsForRealm:realmC];
  811. CHECK_COUNT(3, SyncObject, realmA);
  812. CHECK_COUNT(2, SyncObject, realmB);
  813. CHECK_COUNT(5, SyncObject, realmC);
  814. }
  815. }
  816. /// A client should be able to open multiple Realms and delete objects from each of them.
  817. - (void)testMultipleRealmsDeleteObjects {
  818. NSURL *urlA = CUSTOM_REALM_URL(@"a");
  819. NSURL *urlB = CUSTOM_REALM_URL(@"b");
  820. NSURL *urlC = CUSTOM_REALM_URL(@"c");
  821. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  822. register:self.isParent]
  823. server:[RLMObjectServerTests authServerURL]];
  824. RLMRealm *realmA = [self openRealmForURL:urlA user:user];
  825. RLMRealm *realmB = [self openRealmForURL:urlB user:user];
  826. RLMRealm *realmC = [self openRealmForURL:urlC user:user];
  827. if (self.isParent) {
  828. [self waitForDownloadsForRealm:realmA];
  829. [self waitForDownloadsForRealm:realmB];
  830. [self waitForDownloadsForRealm:realmC];
  831. // Add objects.
  832. [self addSyncObjectsToRealm:realmA
  833. descriptions:@[@"parent-A1", @"parent-A2", @"parent-A3", @"parent-A4"]];
  834. [self addSyncObjectsToRealm:realmB
  835. descriptions:@[@"parent-B1", @"parent-B2", @"parent-B3", @"parent-B4", @"parent-B5"]];
  836. [self addSyncObjectsToRealm:realmC
  837. descriptions:@[@"parent-C1", @"parent-C2"]];
  838. [self waitForUploadsForRealm:realmA];
  839. [self waitForUploadsForRealm:realmB];
  840. [self waitForUploadsForRealm:realmC];
  841. CHECK_COUNT(4, SyncObject, realmA);
  842. CHECK_COUNT(5, SyncObject, realmB);
  843. CHECK_COUNT(2, SyncObject, realmC);
  844. RLMRunChildAndWait();
  845. [self waitForDownloadsForUser:user
  846. realms:@[realmA, realmB, realmC]
  847. realmURLs:@[urlA, urlB, urlC]
  848. expectedCounts:@[@0, @0, @0]];
  849. } else {
  850. // Delete all the objects from the Realms.
  851. [self waitForDownloadsForRealm:realmA];
  852. [self waitForDownloadsForRealm:realmB];
  853. [self waitForDownloadsForRealm:realmC];
  854. CHECK_COUNT(4, SyncObject, realmA);
  855. CHECK_COUNT(5, SyncObject, realmB);
  856. CHECK_COUNT(2, SyncObject, realmC);
  857. [realmA beginWriteTransaction];
  858. [realmA deleteAllObjects];
  859. [realmA commitWriteTransaction];
  860. [realmB beginWriteTransaction];
  861. [realmB deleteAllObjects];
  862. [realmB commitWriteTransaction];
  863. [realmC beginWriteTransaction];
  864. [realmC deleteAllObjects];
  865. [realmC commitWriteTransaction];
  866. [self waitForUploadsForRealm:realmA];
  867. [self waitForUploadsForRealm:realmB];
  868. [self waitForUploadsForRealm:realmC];
  869. CHECK_COUNT(0, SyncObject, realmA);
  870. CHECK_COUNT(0, SyncObject, realmB);
  871. CHECK_COUNT(0, SyncObject, realmC);
  872. }
  873. }
  874. #pragma mark - Session Lifetime
  875. /// When a session opened by a Realm goes out of scope, it should stay alive long enough to finish any waiting uploads.
  876. - (void)testUploadChangesWhenRealmOutOfScope {
  877. const NSInteger OBJECT_COUNT = 10000;
  878. NSURL *url = REALM_URL();
  879. // Log in the user.
  880. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  881. register:self.isParent]
  882. server:[RLMObjectServerTests authServerURL]];
  883. if (self.isParent) {
  884. // Open the Realm in an autorelease pool so that it is destroyed as soon as possible.
  885. @autoreleasepool {
  886. RLMRealm *realm = [self openRealmForURL:url user:user];
  887. [realm beginWriteTransaction];
  888. for (NSInteger i=0; i<OBJECT_COUNT; i++) {
  889. [realm addObject:[[SyncObject alloc] initWithValue:@[[NSString stringWithFormat:@"parent-%@", @(i+1)]]]];
  890. }
  891. [realm commitWriteTransaction];
  892. CHECK_COUNT(OBJECT_COUNT, SyncObject, realm);
  893. }
  894. // Run the sub-test. (Give the upload a bit of time to start.)
  895. // NOTE: This sleep should be fine because:
  896. // - There is currently no API that allows asynchronous coordination for waiting for an upload to begin.
  897. // - A delay longer than the specified one will not affect the outcome of the test.
  898. sleep(2);
  899. RLMRunChildAndWait();
  900. } else {
  901. RLMRealm *realm = [self openRealmForURL:url user:user];
  902. // Wait for download to complete.
  903. [self waitForDownloadsForRealm:realm];
  904. CHECK_COUNT(OBJECT_COUNT, SyncObject, realm);
  905. }
  906. }
  907. #pragma mark - Logging Back In
  908. /// A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in.
  909. - (void)testLogBackInSameRealmUpload {
  910. NSURL *url = REALM_URL();
  911. // Log in the user.
  912. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  913. register:self.isParent]
  914. server:[RLMObjectServerTests authServerURL]];
  915. RLMRealm *realm = [self openRealmForURL:url user:user];
  916. if (self.isParent) {
  917. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
  918. CHECK_COUNT(1, SyncObject, realm);
  919. [self waitForUploadsForRealm:realm];
  920. // Log out the user.
  921. [user logOut];
  922. // Log the user back in.
  923. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  924. register:NO]
  925. server:[RLMObjectServerTests authServerURL]];
  926. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-2", @"parent-3"]];
  927. [self waitForUploadsForRealm:realm];
  928. CHECK_COUNT(3, SyncObject, realm);
  929. RLMRunChildAndWait();
  930. } else {
  931. [self waitForDownloadsForRealm:realm];
  932. CHECK_COUNT(3, SyncObject, realm);
  933. }
  934. }
  935. /// A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in.
  936. - (void)testLogBackInSameRealmDownload {
  937. NSURL *url = REALM_URL();
  938. // Log in the user.
  939. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  940. register:self.isParent]
  941. server:[RLMObjectServerTests authServerURL]];
  942. RLMRealm *realm = [self openRealmForURL:url user:user];
  943. if (self.isParent) {
  944. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
  945. CHECK_COUNT(1, SyncObject, realm);
  946. [self waitForUploadsForRealm:realm];
  947. // Log out the user.
  948. [user logOut];
  949. // Log the user back in.
  950. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  951. register:NO]
  952. server:[RLMObjectServerTests authServerURL]];
  953. RLMRunChildAndWait();
  954. [self waitForDownloadsForRealm:realm];
  955. CHECK_COUNT(3, SyncObject, realm);
  956. } else {
  957. [self waitForDownloadsForRealm:realm];
  958. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2"]];
  959. [self waitForUploadsForRealm:realm];
  960. CHECK_COUNT(3, SyncObject, realm);
  961. }
  962. }
  963. /// A Realm that was opened while a user was logged out should be able to start uploading if the user logs back in.
  964. - (void)testLogBackInDeferredRealmUpload {
  965. NSURL *url = REALM_URL();
  966. // Log in the user.
  967. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  968. register:self.isParent]
  969. server:[RLMObjectServerTests authServerURL]];
  970. NSError *error = nil;
  971. if (self.isParent) {
  972. // Semaphore for knowing when the Realm is successfully opened for sync.
  973. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  974. RLMRealmConfiguration *config = [user configurationWithURL:url fullSynchronization:true];
  975. [user logOut];
  976. // Open a Realm after the user's been logged out.
  977. [self primeSyncManagerWithSemaphore:sema];
  978. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
  979. XCTAssertNil(error, @"Error when opening Realm: %@", error);
  980. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
  981. CHECK_COUNT(1, SyncObject, realm);
  982. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  983. register:NO]
  984. server:[RLMObjectServerTests authServerURL]];
  985. // Wait for the Realm's session to be bound.
  986. WAIT_FOR_SEMAPHORE(sema, 30);
  987. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-2", @"parent-3"]];
  988. [self waitForUploadsForRealm:realm];
  989. CHECK_COUNT(3, SyncObject, realm);
  990. RLMRunChildAndWait();
  991. } else {
  992. RLMRealm *realm = [self openRealmForURL:url user:user];
  993. XCTAssertNil(error, @"Error when opening Realm: %@", error);
  994. [self waitForDownloadsForRealm:realm];
  995. CHECK_COUNT(3, SyncObject, realm);
  996. }
  997. }
  998. /// A Realm that was opened while a user was logged out should be able to start downloading if the user logs back in.
  999. - (void)testLogBackInDeferredRealmDownload {
  1000. NSURL *url = REALM_URL();
  1001. // Log in the user.
  1002. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1003. register:self.isParent]
  1004. server:[RLMObjectServerTests authServerURL]];
  1005. NSError *error = nil;
  1006. if (self.isParent) {
  1007. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  1008. RLMRunChildAndWait();
  1009. RLMRealmConfiguration *config = [user configurationWithURL:url fullSynchronization:true];
  1010. [user logOut];
  1011. // Open a Realm after the user's been logged out.
  1012. [self primeSyncManagerWithSemaphore:sema];
  1013. RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
  1014. XCTAssertNil(error, @"Error when opening Realm: %@", error);
  1015. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
  1016. CHECK_COUNT(1, SyncObject, realm);
  1017. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1018. register:NO]
  1019. server:[RLMObjectServerTests authServerURL]];
  1020. // Wait for the Realm's session to be bound.
  1021. WAIT_FOR_SEMAPHORE(sema, 30);
  1022. [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@4]];
  1023. } else {
  1024. RLMRealm *realm = [self openRealmForURL:url user:user];
  1025. XCTAssertNil(error, @"Error when opening Realm: %@", error);
  1026. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
  1027. [self waitForUploadsForRealm:realm];
  1028. CHECK_COUNT(3, SyncObject, realm);
  1029. }
  1030. }
  1031. /// After logging back in, a Realm whose path has been opened for the first time should properly upload changes.
  1032. - (void)testLogBackInOpenFirstTimePathUpload {
  1033. NSURL *url = REALM_URL();
  1034. // Log in the user.
  1035. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1036. register:self.isParent]
  1037. server:[RLMObjectServerTests authServerURL]];
  1038. // Now run a basic multi-client test.
  1039. if (self.isParent) {
  1040. // Log out the user.
  1041. [user logOut];
  1042. // Log the user back in.
  1043. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1044. register:NO]
  1045. server:[RLMObjectServerTests authServerURL]];
  1046. // Open the Realm (for the first time).
  1047. RLMRealm *realm = [self openRealmForURL:url user:user];
  1048. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2"]];
  1049. [self waitForUploadsForRealm:realm];
  1050. CHECK_COUNT(2, SyncObject, realm);
  1051. RLMRunChildAndWait();
  1052. } else {
  1053. RLMRealm *realm = [self openRealmForURL:url user:user];
  1054. // Add objects.
  1055. [self waitForDownloadsForRealm:realm];
  1056. CHECK_COUNT(2, SyncObject, realm);
  1057. }
  1058. }
  1059. /// After logging back in, a Realm whose path has been opened for the first time should properly download changes.
  1060. - (void)testLogBackInOpenFirstTimePathDownload {
  1061. NSURL *url = REALM_URL();
  1062. // Log in the user.
  1063. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1064. register:self.isParent]
  1065. server:[RLMObjectServerTests authServerURL]];
  1066. // Now run a basic multi-client test.
  1067. if (self.isParent) {
  1068. // Log out the user.
  1069. [user logOut];
  1070. // Log the user back in.
  1071. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1072. register:NO]
  1073. server:[RLMObjectServerTests authServerURL]];
  1074. // Open the Realm (for the first time).
  1075. RLMRealm *realm = [self openRealmForURL:url user:user];
  1076. // Run the sub-test.
  1077. RLMRunChildAndWait();
  1078. [self waitForDownloadsForRealm:realm];
  1079. CHECK_COUNT(2, SyncObject, realm);
  1080. } else {
  1081. RLMRealm *realm = [self openRealmForURL:url user:user];
  1082. // Add objects.
  1083. [self waitForDownloadsForRealm:realm];
  1084. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2"]];
  1085. [self waitForUploadsForRealm:realm];
  1086. CHECK_COUNT(2, SyncObject, realm);
  1087. }
  1088. }
  1089. /// If a client logs in, connects, logs out, and logs back in, sync should properly upload changes for a new
  1090. /// `RLMRealm` that is opened for the same path as a previously-opened Realm.
  1091. - (void)testLogBackInReopenRealmUpload {
  1092. NSURL *url = REALM_URL();
  1093. // Log in the user.
  1094. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1095. register:self.isParent]
  1096. server:[RLMObjectServerTests authServerURL]];
  1097. // Open the Realm
  1098. RLMRealm *realm = [self openRealmForURL:url user:user];
  1099. if (self.isParent) {
  1100. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
  1101. [self waitForUploadsForRealm:realm];
  1102. CHECK_COUNT(1, SyncObject, realm);
  1103. // Log out the user.
  1104. [user logOut];
  1105. // Log the user back in.
  1106. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1107. register:NO]
  1108. server:[RLMObjectServerTests authServerURL]];
  1109. // Open the Realm again.
  1110. realm = [self immediatelyOpenRealmForURL:url user:user];
  1111. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3", @"child-4"]];
  1112. CHECK_COUNT(5, SyncObject, realm);
  1113. [self waitForUploadsForRealm:realm];
  1114. RLMRunChildAndWait();
  1115. } else {
  1116. [self waitForDownloadsForRealm:realm];
  1117. CHECK_COUNT(5, SyncObject, realm);
  1118. }
  1119. }
  1120. /// If a client logs in, connects, logs out, and logs back in, sync should properly download changes for a new
  1121. /// `RLMRealm` that is opened for the same path as a previously-opened Realm.
  1122. - (void)testLogBackInReopenRealmDownload {
  1123. NSURL *url = REALM_URL();
  1124. // Log in the user.
  1125. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1126. register:self.isParent]
  1127. server:[RLMObjectServerTests authServerURL]];
  1128. // Open the Realm
  1129. RLMRealm *realm = [self openRealmForURL:url user:user];
  1130. if (self.isParent) {
  1131. [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
  1132. [self waitForUploadsForRealm:realm];
  1133. XCTAssert([SyncObject allObjectsInRealm:realm].count == 1, @"Expected 1 item");
  1134. // Log out the user.
  1135. [user logOut];
  1136. // Log the user back in.
  1137. user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1138. register:NO]
  1139. server:[RLMObjectServerTests authServerURL]];
  1140. // Run the sub-test.
  1141. RLMRunChildAndWait();
  1142. // Open the Realm again and get the items.
  1143. realm = [self immediatelyOpenRealmForURL:url user:user];
  1144. [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@5]];
  1145. } else {
  1146. // Add objects.
  1147. [self waitForDownloadsForRealm:realm];
  1148. CHECK_COUNT(1, SyncObject, realm);
  1149. [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3", @"child-4"]];
  1150. [self waitForUploadsForRealm:realm];
  1151. CHECK_COUNT(5, SyncObject, realm);
  1152. }
  1153. }
  1154. #pragma mark - Session suspend and resume
  1155. - (void)testSuspendAndResume {
  1156. NSURL *urlA = CUSTOM_REALM_URL(@"a");
  1157. NSURL *urlB = CUSTOM_REALM_URL(@"b");
  1158. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1159. register:self.isParent]
  1160. server:[RLMObjectServerTests authServerURL]];
  1161. RLMRealm *realmA = [self openRealmForURL:urlA user:user];
  1162. RLMRealm *realmB = [self openRealmForURL:urlB user:user];
  1163. if (self.isParent) {
  1164. [self waitForDownloadsForRealm:realmA];
  1165. [self waitForDownloadsForRealm:realmB];
  1166. CHECK_COUNT(0, SyncObject, realmA);
  1167. CHECK_COUNT(0, SyncObject, realmB);
  1168. // Suspend the session for realm A and then add an object to each Realm
  1169. RLMSyncSession *sessionA = [RLMSyncSession sessionForRealm:realmA];
  1170. [sessionA suspend];
  1171. [self addSyncObjectsToRealm:realmA descriptions:@[@"child-A1"]];
  1172. [self addSyncObjectsToRealm:realmB descriptions:@[@"child-B1"]];
  1173. [self waitForUploadsForRealm:realmB];
  1174. RLMRunChildAndWait();
  1175. // A should still be 1 since it's suspended. If it wasn't suspended, it
  1176. // should have downloaded before B due to the ordering in the child.
  1177. [self waitForDownloadsForRealm:realmB];
  1178. CHECK_COUNT(1, SyncObject, realmA);
  1179. CHECK_COUNT(3, SyncObject, realmB);
  1180. // A should see the other two from the child after resuming
  1181. [sessionA resume];
  1182. [self waitForDownloadsForRealm:realmA];
  1183. CHECK_COUNT(3, SyncObject, realmA);
  1184. } else {
  1185. // Child shouldn't see the object in A
  1186. [self waitForDownloadsForRealm:realmA];
  1187. [self waitForDownloadsForRealm:realmB];
  1188. CHECK_COUNT(0, SyncObject, realmA);
  1189. CHECK_COUNT(1, SyncObject, realmB);
  1190. [self addSyncObjectsToRealm:realmA descriptions:@[@"child-A2", @"child-A3"]];
  1191. [self waitForUploadsForRealm:realmA];
  1192. [self addSyncObjectsToRealm:realmB descriptions:@[@"child-B2", @"child-B3"]];
  1193. [self waitForUploadsForRealm:realmB];
  1194. CHECK_COUNT(2, SyncObject, realmA);
  1195. CHECK_COUNT(3, SyncObject, realmB);
  1196. }
  1197. }
  1198. #pragma mark - Client reset
  1199. /// Ensure that a client reset error is propagated up to the binding successfully.
  1200. - (void)testClientReset {
  1201. NSURL *url = REALM_URL();
  1202. NSString *sessionName = NSStringFromSelector(_cmd);
  1203. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:sessionName
  1204. register:true]
  1205. server:[RLMObjectServerTests authServerURL]];
  1206. // Open the Realm
  1207. __attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:user];
  1208. __block NSError *theError = nil;
  1209. XCTestExpectation *ex = [self expectationWithDescription:@"Waiting for error handler to be called..."];
  1210. [RLMSyncManager sharedManager].errorHandler = ^void(NSError *error, RLMSyncSession *session) {
  1211. // Make sure we're actually looking at the right session.
  1212. XCTAssertTrue([[session.realmURL absoluteString] rangeOfString:sessionName].location != NSNotFound);
  1213. theError = error;
  1214. [ex fulfill];
  1215. };
  1216. [user simulateClientResetErrorForSession:url];
  1217. [self waitForExpectationsWithTimeout:10 handler:nil];
  1218. XCTAssertNotNil(theError);
  1219. XCTAssertTrue(theError.code == RLMSyncErrorClientResetError);
  1220. NSString *pathValue = [theError rlmSync_clientResetBackedUpRealmPath];
  1221. XCTAssertNotNil(pathValue);
  1222. // Sanity check the recovery path.
  1223. NSString *recoveryPath = @"io.realm.object-server-recovered-realms/recovered_realm";
  1224. XCTAssertTrue([pathValue rangeOfString:recoveryPath].location != NSNotFound);
  1225. XCTAssertNotNil([theError rlmSync_errorActionToken]);
  1226. }
  1227. /// Test manually initiating client reset.
  1228. - (void)testClientResetManualInitiation {
  1229. NSURL *url = REALM_URL();
  1230. NSString *sessionName = NSStringFromSelector(_cmd);
  1231. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:sessionName
  1232. register:true]
  1233. server:[RLMObjectServerTests authServerURL]];
  1234. __block NSError *theError = nil;
  1235. @autoreleasepool {
  1236. __attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:user];
  1237. XCTestExpectation *ex = [self expectationWithDescription:@"Waiting for error handler to be called..."];
  1238. [RLMSyncManager sharedManager].errorHandler = ^void(NSError *error, RLMSyncSession *session) {
  1239. // Make sure we're actually looking at the right session.
  1240. XCTAssertTrue([[session.realmURL absoluteString] rangeOfString:sessionName].location != NSNotFound);
  1241. theError = error;
  1242. [ex fulfill];
  1243. };
  1244. [user simulateClientResetErrorForSession:url];
  1245. [self waitForExpectationsWithTimeout:10 handler:nil];
  1246. XCTAssertNotNil(theError);
  1247. }
  1248. // At this point the Realm should be invalidated and client reset should be possible.
  1249. NSString *pathValue = [theError rlmSync_clientResetBackedUpRealmPath];
  1250. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:pathValue]);
  1251. [RLMSyncSession immediatelyHandleError:[theError rlmSync_errorActionToken]];
  1252. XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:pathValue]);
  1253. }
  1254. #pragma mark - Progress Notifications
  1255. - (void)testStreamingDownloadNotifier {
  1256. const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
  1257. NSURL *url = REALM_URL();
  1258. // Log in the user.
  1259. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1260. register:self.isParent]
  1261. server:[RLMObjectServerTests authServerURL]];
  1262. __block NSInteger callCount = 0;
  1263. __block NSUInteger transferred = 0;
  1264. __block NSUInteger transferrable = 0;
  1265. // Open the Realm
  1266. RLMRealm *realm = [self openRealmForURL:url user:user];
  1267. if (self.isParent) {
  1268. __block BOOL hasBeenFulfilled = NO;
  1269. // Register a notifier.
  1270. RLMSyncSession *session = [user sessionForURL:url];
  1271. XCTAssertNotNil(session);
  1272. XCTestExpectation *ex = [self expectationWithDescription:@"streaming-download-notifier"];
  1273. RLMProgressNotificationToken *token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionDownload
  1274. mode:RLMSyncProgressModeReportIndefinitely
  1275. block:^(NSUInteger xfr, NSUInteger xfb) {
  1276. // Make sure the values are increasing, and update our stored copies.
  1277. XCTAssert(xfr >= transferred);
  1278. XCTAssert(xfb >= transferrable);
  1279. transferred = xfr;
  1280. transferrable = xfb;
  1281. callCount++;
  1282. if (transferrable > 0 && transferred >= transferrable && !hasBeenFulfilled) {
  1283. [ex fulfill];
  1284. hasBeenFulfilled = YES;
  1285. }
  1286. }];
  1287. // Wait for the child process to upload everything.
  1288. RLMRunChildAndWait();
  1289. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  1290. [token invalidate];
  1291. // The notifier should have been called at least twice: once at the beginning and at least once
  1292. // to report progress.
  1293. XCTAssert(callCount > 1);
  1294. XCTAssert(transferred >= transferrable,
  1295. @"Transferred (%@) needs to be greater than or equal to transferrable (%@)",
  1296. @(transferred), @(transferrable));
  1297. } else {
  1298. // Write lots of data to the Realm, then wait for it to be uploaded.
  1299. [realm beginWriteTransaction];
  1300. for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
  1301. [realm addObject:[HugeSyncObject object]];
  1302. }
  1303. [realm commitWriteTransaction];
  1304. [self waitForUploadsForRealm:realm];
  1305. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1306. }
  1307. }
  1308. - (void)testStreamingUploadNotifier {
  1309. const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
  1310. NSURL *url = REALM_URL();
  1311. // Log in the user.
  1312. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1313. register:self.isParent]
  1314. server:[RLMObjectServerTests authServerURL]];
  1315. __block NSInteger callCount = 0;
  1316. __block NSUInteger transferred = 0;
  1317. __block NSUInteger transferrable = 0;
  1318. // Open the Realm
  1319. RLMRealm *realm = [self openRealmForURL:url user:user];
  1320. // Register a notifier.
  1321. RLMSyncSession *session = [user sessionForURL:url];
  1322. XCTAssertNotNil(session);
  1323. XCTestExpectation *ex = [self expectationWithDescription:@"streaming-upload-expectation"];
  1324. auto token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionUpload
  1325. mode:RLMSyncProgressModeReportIndefinitely
  1326. block:^(NSUInteger xfr, NSUInteger xfb) {
  1327. // Make sure the values are
  1328. // increasing, and update our
  1329. // stored copies.
  1330. XCTAssert(xfr >= transferred);
  1331. XCTAssert(xfb >= transferrable);
  1332. transferred = xfr;
  1333. transferrable = xfb;
  1334. callCount++;
  1335. if (transferred > 0 && transferred >= transferrable && transferrable > 1000000 * NUMBER_OF_BIG_OBJECTS) {
  1336. [ex fulfill];
  1337. }
  1338. }];
  1339. // Upload lots of data
  1340. [realm beginWriteTransaction];
  1341. for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
  1342. [realm addObject:[HugeSyncObject object]];
  1343. }
  1344. [realm commitWriteTransaction];
  1345. // Wait for upload to begin and finish
  1346. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  1347. [token invalidate];
  1348. // The notifier should have been called at least twice: once at the beginning and at least once
  1349. // to report progress.
  1350. XCTAssert(callCount > 1);
  1351. XCTAssert(transferred >= transferrable,
  1352. @"Transferred (%@) needs to be greater than or equal to transferrable (%@)",
  1353. @(transferred), @(transferrable));
  1354. }
  1355. #pragma mark - Download Realm
  1356. - (void)testDownloadRealm {
  1357. const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
  1358. NSURL *url = REALM_URL();
  1359. // Log in the user.
  1360. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1361. register:self.isParent]
  1362. server:[RLMObjectServerTests authServerURL]];
  1363. if (self.isParent) {
  1364. // Wait for the child process to upload everything.
  1365. RLMRunChildAndWait();
  1366. XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
  1367. RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
  1368. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]);
  1369. [RLMRealm asyncOpenWithConfiguration:c
  1370. callbackQueue:dispatch_get_main_queue()
  1371. callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
  1372. XCTAssertNil(error);
  1373. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1374. [ex fulfill];
  1375. }];
  1376. NSUInteger (^fileSize)(NSString *) = ^NSUInteger(NSString *path) {
  1377. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  1378. if (attributes)
  1379. return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
  1380. return 0;
  1381. };
  1382. XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1383. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  1384. XCTAssertGreaterThan(fileSize(c.pathOnDisk), 0U);
  1385. XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1386. } else {
  1387. RLMRealm *realm = [self openRealmForURL:url user:user];
  1388. // Write lots of data to the Realm, then wait for it to be uploaded.
  1389. [realm beginWriteTransaction];
  1390. for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
  1391. [realm addObject:[HugeSyncObject object]];
  1392. }
  1393. [realm commitWriteTransaction];
  1394. [self waitForUploadsForRealm:realm];
  1395. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1396. }
  1397. }
  1398. - (void)testDownloadAlreadyOpenRealm {
  1399. const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
  1400. NSURL *url = REALM_URL();
  1401. // Log in the user.
  1402. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1403. register:self.isParent]
  1404. server:[RLMObjectServerTests authServerURL]];
  1405. if (!self.isParent) {
  1406. RLMRealm *realm = [self openRealmForURL:url user:user];
  1407. // Write lots of data to the Realm, then wait for it to be uploaded.
  1408. [realm beginWriteTransaction];
  1409. for (NSInteger i = 0; i < NUMBER_OF_BIG_OBJECTS; i++) {
  1410. [realm addObject:[HugeSyncObject object]];
  1411. }
  1412. [realm commitWriteTransaction];
  1413. [self waitForUploadsForRealm:realm];
  1414. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1415. return;
  1416. }
  1417. XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
  1418. RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
  1419. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]);
  1420. RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil];
  1421. CHECK_COUNT(0, HugeSyncObject, realm);
  1422. [realm.syncSession suspend];
  1423. // Wait for the child process to upload everything.
  1424. RLMRunChildAndWait();
  1425. auto fileSize = ^NSUInteger(NSString *path) {
  1426. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  1427. return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
  1428. };
  1429. NSUInteger sizeBefore = fileSize(c.pathOnDisk);
  1430. XCTAssertGreaterThan(sizeBefore, 0U);
  1431. XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1432. [RLMRealm asyncOpenWithConfiguration:c
  1433. callbackQueue:dispatch_get_main_queue()
  1434. callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
  1435. XCTAssertNil(error);
  1436. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1437. [ex fulfill];
  1438. }];
  1439. [realm.syncSession resume];
  1440. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1441. XCTAssertGreaterThan(fileSize(c.pathOnDisk), sizeBefore);
  1442. XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1443. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1444. (void)[realm configuration];
  1445. }
  1446. - (void)testDownloadCancelsOnAuthError {
  1447. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1448. register:self.isParent]
  1449. server:[RLMObjectServerTests authServerURL]];
  1450. auto c = [user configurationWithURL:[NSURL URLWithString:@"realm://127.0.0.1:9080/invalid"] fullSynchronization:true];
  1451. auto ex = [self expectationWithDescription:@"async open"];
  1452. [RLMRealm asyncOpenWithConfiguration:c callbackQueue:dispatch_get_main_queue()
  1453. callback:^(RLMRealm *realm, NSError *error) {
  1454. XCTAssertNil(realm);
  1455. XCTAssertNotNil(error);
  1456. [ex fulfill];
  1457. }];
  1458. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1459. }
  1460. #pragma mark - Compact on Launch
  1461. - (void)testCompactOnLaunch {
  1462. NSURL *url = REALM_URL();
  1463. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1464. register:YES]
  1465. server:[RLMObjectServerTests authServerURL]];
  1466. NSString *path;
  1467. // Create a large object and then delete it in the next transaction so that
  1468. // the file is bloated
  1469. @autoreleasepool {
  1470. auto realm = [self openRealmForURL:url user:user];
  1471. [realm beginWriteTransaction];
  1472. [realm addObject:[HugeSyncObject object]];
  1473. [realm commitWriteTransaction];
  1474. [self waitForUploadsForRealm:realm];
  1475. [realm beginWriteTransaction];
  1476. [realm deleteAllObjects];
  1477. [realm commitWriteTransaction];
  1478. [self waitForUploadsForRealm:realm];
  1479. [self waitForDownloadsForRealm:realm];
  1480. path = realm.configuration.pathOnDisk;
  1481. }
  1482. auto fileManager = NSFileManager.defaultManager;
  1483. auto initialSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue];
  1484. // Reopen the file with a shouldCompactOnLaunch block and verify that it is
  1485. // actually compacted
  1486. auto config = [user configurationWithURL:url fullSynchronization:true];
  1487. __block bool blockCalled = false;
  1488. __block NSUInteger usedSize = 0;
  1489. config.shouldCompactOnLaunch = ^(NSUInteger, NSUInteger used) {
  1490. usedSize = used;
  1491. blockCalled = true;
  1492. return YES;
  1493. };
  1494. @autoreleasepool {
  1495. [RLMRealm realmWithConfiguration:config error:nil];
  1496. }
  1497. XCTAssertTrue(blockCalled);
  1498. auto finalSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue];
  1499. XCTAssertLessThan(finalSize, initialSize);
  1500. XCTAssertLessThanOrEqual(finalSize, usedSize + 4096U);
  1501. }
  1502. #pragma mark - Offline Client Reset
  1503. - (void)testOfflineClientReset {
  1504. NSError *error;
  1505. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1506. register:YES]
  1507. server:[RLMObjectServerTests authServerURL]];
  1508. NSURL *sourceFileURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"sync-1.x" withExtension:@"realm"];
  1509. NSString *fileName = [NSString stringWithFormat:@"%@.realm", [NSUUID new]];
  1510. NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
  1511. [NSFileManager.defaultManager copyItemAtURL:sourceFileURL toURL:fileURL error:&error];
  1512. XCTAssertNil(error);
  1513. if (error) {
  1514. return;
  1515. }
  1516. RLMRealmConfiguration *configuration = [user configurationWithURL:REALM_URL() fullSynchronization:true];
  1517. RLMSyncConfiguration *syncConfig = configuration.syncConfiguration;
  1518. syncConfig.customFileURL = fileURL;
  1519. configuration.syncConfiguration = syncConfig;
  1520. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error];
  1521. XCTAssertNil(realm);
  1522. XCTAssertEqualObjects(error.domain, RLMErrorDomain);
  1523. XCTAssertEqual(error.code, RLMErrorIncompatibleSyncedFile);
  1524. RLMRealmConfiguration *backupConfiguration = error.userInfo[RLMBackupRealmConfigurationErrorKey];
  1525. XCTAssertNotNil(backupConfiguration);
  1526. // Open the backup Realm with a schema subset since it was created using the schema from .NET's unit tests.
  1527. // The Person class is declared in SwiftObjectServerTests.swift.
  1528. backupConfiguration.objectClasses = @[NSClassFromString(@"Person")];
  1529. error = nil;
  1530. RLMRealm *backupRealm = [RLMRealm realmWithConfiguration:backupConfiguration error:&error];
  1531. XCTAssertNotNil(backupRealm);
  1532. XCTAssertNil(error);
  1533. RLMResults *people = [backupRealm allObjects:@"Person"];
  1534. XCTAssertEqual(people.count, 1u);
  1535. XCTAssertEqualObjects([people[0] valueForKey:@"FirstName"], @"John");
  1536. XCTAssertEqualObjects([people[0] valueForKey:@"LastName"], @"Smith");
  1537. error = nil;
  1538. realm = [RLMRealm realmWithConfiguration:configuration error:&error];
  1539. XCTAssertNotNil(realm);
  1540. XCTAssertNil(error);
  1541. }
  1542. #pragma clang diagnostic push
  1543. #pragma clang diagnostic ignored "-Wdeprecated"
  1544. - (void)testAutomaticSyncConfiguration {
  1545. NSURL *server = [RLMObjectServerTests authServerURL];
  1546. // Automatic configuration should throw when there are no logged-in users.
  1547. XCTAssertThrows([RLMSyncConfiguration automaticConfiguration]);
  1548. RLMSyncCredentials *credsA = [RLMObjectServerTests basicCredentialsWithName:@"a" register:YES];
  1549. RLMSyncUser *userA = [self logInUserForCredentials:credsA server:server];
  1550. // Now that there's a logged-in user, we should be able to retrieve the configuration.
  1551. RLMRealmConfiguration *configuration = [RLMSyncConfiguration automaticConfiguration];
  1552. XCTAssert(configuration);
  1553. @autoreleasepool {
  1554. // And open it successfully.
  1555. RLMRealm *realm = [self openRealmWithConfiguration:configuration];
  1556. [self waitForDownloadsForRealm:realm];
  1557. }
  1558. RLMSyncCredentials *credsB = [RLMObjectServerTests basicCredentialsWithName:@"b" register:YES];
  1559. RLMSyncUser *userB = [self logInUserForCredentials:credsB server:server];
  1560. // Automatic configuration should throw since there's more than one logged-in user.
  1561. XCTAssertThrows([RLMSyncConfiguration automaticConfiguration]);
  1562. // It should still be possible to explicitly retrieve an automatic configuration for a user.
  1563. RLMRealmConfiguration *configurationA = [RLMSyncConfiguration automaticConfigurationForUser:userA];
  1564. XCTAssert(configurationA);
  1565. XCTAssertEqualObjects(configuration.syncConfiguration, configurationA.syncConfiguration);
  1566. RLMRealmConfiguration *configurationB = [RLMSyncConfiguration automaticConfigurationForUser:userB];
  1567. XCTAssert(configurationB);
  1568. XCTAssertNotEqualObjects(configuration.syncConfiguration, configurationB.syncConfiguration);
  1569. [userB logOut];
  1570. // Now that we're back to a single logged-in user, we should be able to retrieve the configuration.
  1571. configuration = [RLMSyncConfiguration automaticConfiguration];
  1572. XCTAssert(configuration);
  1573. }
  1574. #pragma clang diagnostic pop
  1575. #pragma mark - Partial sync
  1576. - (void)waitForKeyPath:(NSString *)keyPath object:(id)object value:(id)value {
  1577. [self waitForExpectations:@[[[XCTKVOExpectation alloc] initWithKeyPath:keyPath object:object expectedValue:value]] timeout:20.0];
  1578. }
  1579. - (void)testPartialSync {
  1580. // Make credentials.
  1581. NSString *name = NSStringFromSelector(_cmd);
  1582. NSURL *server = [RLMObjectServerTests authServerURL];
  1583. // Log in and populate the Realm.
  1584. @autoreleasepool {
  1585. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:YES];
  1586. RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
  1587. RLMRealmConfiguration *configuration = [user configuration];
  1588. RLMRealm *realm = [self openRealmWithConfiguration:configuration];
  1589. [realm beginWriteTransaction];
  1590. // FIXME: make this less hideous
  1591. // Add ten of each object
  1592. [realm addObject:[PartialSyncObjectA objectWithNumber:0 string:@"realm"]];
  1593. [realm addObject:[PartialSyncObjectA objectWithNumber:1 string:@""]];
  1594. [realm addObject:[PartialSyncObjectA objectWithNumber:2 string:@""]];
  1595. [realm addObject:[PartialSyncObjectA objectWithNumber:3 string:@""]];
  1596. [realm addObject:[PartialSyncObjectA objectWithNumber:4 string:@"realm"]];
  1597. [realm addObject:[PartialSyncObjectA objectWithNumber:5 string:@"sync"]];
  1598. [realm addObject:[PartialSyncObjectA objectWithNumber:6 string:@"partial"]];
  1599. [realm addObject:[PartialSyncObjectA objectWithNumber:7 string:@"partial"]];
  1600. [realm addObject:[PartialSyncObjectA objectWithNumber:8 string:@"partial"]];
  1601. [realm addObject:[PartialSyncObjectA objectWithNumber:9 string:@"partial"]];
  1602. [realm addObject:[PartialSyncObjectB objectWithNumber:0 firstString:@"" secondString:@""]];
  1603. [realm addObject:[PartialSyncObjectB objectWithNumber:1 firstString:@"" secondString:@""]];
  1604. [realm addObject:[PartialSyncObjectB objectWithNumber:2 firstString:@"" secondString:@""]];
  1605. [realm addObject:[PartialSyncObjectB objectWithNumber:3 firstString:@"" secondString:@""]];
  1606. [realm addObject:[PartialSyncObjectB objectWithNumber:4 firstString:@"" secondString:@""]];
  1607. [realm addObject:[PartialSyncObjectB objectWithNumber:5 firstString:@"" secondString:@""]];
  1608. [realm addObject:[PartialSyncObjectB objectWithNumber:6 firstString:@"" secondString:@""]];
  1609. [realm addObject:[PartialSyncObjectB objectWithNumber:7 firstString:@"" secondString:@""]];
  1610. [realm addObject:[PartialSyncObjectB objectWithNumber:8 firstString:@"" secondString:@""]];
  1611. [realm addObject:[PartialSyncObjectB objectWithNumber:9 firstString:@"" secondString:@""]];
  1612. [realm commitWriteTransaction];
  1613. [self waitForUploadsForRealm:realm];
  1614. }
  1615. // Log back in and do partial sync stuff.
  1616. @autoreleasepool {
  1617. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:NO];
  1618. RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
  1619. RLMRealmConfiguration *configuration = [user configuration];
  1620. RLMRealm *realm = [self openRealmWithConfiguration:configuration];
  1621. // Perform some partial sync queries
  1622. RLMResults *objects = [PartialSyncObjectA objectsInRealm:realm where:@"number > 5"];
  1623. RLMSyncSubscription *subscription = [objects subscribeWithName:@"query"];
  1624. // Wait for the results to become available.
  1625. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
  1626. // Verify that we got what we're looking for
  1627. XCTAssertEqual(objects.count, 4U);
  1628. for (PartialSyncObjectA *object in objects) {
  1629. XCTAssertGreaterThan(object.number, 5);
  1630. XCTAssertEqualObjects(object.string, @"partial");
  1631. }
  1632. // Verify that we didn't get any other objects
  1633. XCTAssertEqual([PartialSyncObjectA allObjectsInRealm:realm].count, objects.count);
  1634. XCTAssertEqual([PartialSyncObjectB allObjectsInRealm:realm].count, 0u);
  1635. // Create a subscription with the same name but a different query. This should trigger an error.
  1636. RLMResults *objects2 = [PartialSyncObjectA objectsInRealm:realm where:@"number < 5"];
  1637. RLMSyncSubscription *subscription2 = [objects2 subscribeWithName:@"query"];
  1638. // Wait for the error to be reported.
  1639. [self waitForKeyPath:@"state" object:subscription2 value:@(RLMSyncSubscriptionStateError)];
  1640. XCTAssertNotNil(subscription2.error);
  1641. // Unsubscribe from the query, and ensure that it correctly transitions to the invalidated state.
  1642. [subscription unsubscribe];
  1643. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateInvalidated)];
  1644. }
  1645. }
  1646. - (RLMRealm *)partialRealmWithName:(SEL)sel {
  1647. NSString *name = NSStringFromSelector(sel);
  1648. NSURL *server = [RLMObjectServerTests authServerURL];
  1649. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:YES];
  1650. RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
  1651. RLMRealmConfiguration *configuration = [user configuration];
  1652. return [self openRealmWithConfiguration:configuration];
  1653. }
  1654. - (void)testAllSubscriptionsReportsNewlyCreatedSubscription {
  1655. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1656. XCTAssertEqual(0U, realm.subscriptions.count);
  1657. RLMSyncSubscription *subscription = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 5"]
  1658. subscribeWithName:@"query"];
  1659. // Should still be 0 because the subscription is created asynchronously
  1660. XCTAssertEqual(0U, realm.subscriptions.count);
  1661. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
  1662. XCTAssertEqual(1U, realm.subscriptions.count);
  1663. RLMSyncSubscription *subscription2 = realm.subscriptions.firstObject;
  1664. XCTAssertEqualObjects(@"query", subscription2.name);
  1665. XCTAssertEqual(RLMSyncSubscriptionStateComplete, subscription2.state);
  1666. XCTAssertNil(subscription2.error);
  1667. }
  1668. - (void)testAllSubscriptionsDoesNotReportLocalError {
  1669. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1670. RLMSyncSubscription *subscription1 = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 5"]
  1671. subscribeWithName:@"query"];
  1672. [self waitForKeyPath:@"state" object:subscription1 value:@(RLMSyncSubscriptionStateComplete)];
  1673. RLMSyncSubscription *subscription2 = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 6"]
  1674. subscribeWithName:@"query"];
  1675. [self waitForKeyPath:@"state" object:subscription2 value:@(RLMSyncSubscriptionStateError)];
  1676. XCTAssertEqual(1U, realm.subscriptions.count);
  1677. }
  1678. - (void)testAllSubscriptionsReportsServerError {
  1679. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1680. RLMSyncSubscription *subscription = [[PersonObject objectsInRealm:realm where:@"SUBQUERY(parents, $p1, $p1.age < 31 AND SUBQUERY($p1.parents, $p2, $p2.age > 35 AND $p2.name == 'Michael').@count > 0).@count > 0"]
  1681. subscribeWithName:@"query"];
  1682. XCTAssertEqual(0U, realm.subscriptions.count);
  1683. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateError)];
  1684. XCTAssertEqual(1U, realm.subscriptions.count);
  1685. RLMSyncSubscription *subscription2 = realm.subscriptions.lastObject;
  1686. XCTAssertEqualObjects(@"query", subscription2.name);
  1687. XCTAssertEqual(RLMSyncSubscriptionStateError, subscription2.state);
  1688. XCTAssertNotNil(subscription2.error);
  1689. }
  1690. - (void)testUnsubscribeUsingOriginalSubscriptionObservingFetched {
  1691. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1692. RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1693. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
  1694. XCTAssertEqual(1U, realm.subscriptions.count);
  1695. RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
  1696. [original unsubscribe];
  1697. [self waitForKeyPath:@"state" object:fetched value:@(RLMSyncSubscriptionStateInvalidated)];
  1698. XCTAssertEqual(0U, realm.subscriptions.count);
  1699. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, original.state);
  1700. // XCTKVOExpecatation retains the object and releases it sometime later on
  1701. // a background thread, which causes issues if the realm is closed after
  1702. // we reset the global state
  1703. realm->_realm->close();
  1704. }
  1705. - (void)testUnsubscribeUsingFetchedSubscriptionObservingFetched {
  1706. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1707. RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1708. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
  1709. XCTAssertEqual(1U, realm.subscriptions.count);
  1710. RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
  1711. [fetched unsubscribe];
  1712. [self waitForKeyPath:@"state" object:fetched value:@(RLMSyncSubscriptionStateInvalidated)];
  1713. XCTAssertEqual(0U, realm.subscriptions.count);
  1714. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, original.state);
  1715. // XCTKVOExpecatation retains the object and releases it sometime later on
  1716. // a background thread, which causes issues if the realm is closed after
  1717. // we reset the global state
  1718. realm->_realm->close();
  1719. }
  1720. - (void)testUnsubscribeUsingFetchedSubscriptionObservingOriginal {
  1721. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1722. RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1723. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
  1724. XCTAssertEqual(1U, realm.subscriptions.count);
  1725. RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
  1726. [fetched unsubscribe];
  1727. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateInvalidated)];
  1728. XCTAssertEqual(0U, realm.subscriptions.count);
  1729. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, fetched.state);
  1730. }
  1731. - (void)testSubscriptionWithName {
  1732. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1733. XCTAssertNil([realm subscriptionWithName:@"query"]);
  1734. RLMSyncSubscription *subscription = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1735. XCTAssertNil([realm subscriptionWithName:@"query"]);
  1736. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
  1737. XCTAssertNotNil([realm subscriptionWithName:@"query"]);
  1738. XCTAssertNil([realm subscriptionWithName:@"query2"]);
  1739. RLMSyncSubscription *subscription2 = [realm subscriptionWithName:@"query"];
  1740. XCTAssertEqualObjects(@"query", subscription2.name);
  1741. XCTAssertEqual(RLMSyncSubscriptionStateComplete, subscription2.state);
  1742. XCTAssertNil(subscription2.error);
  1743. [subscription unsubscribe];
  1744. XCTAssertNotNil([realm subscriptionWithName:@"query"]);
  1745. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateInvalidated)];
  1746. XCTAssertNil([realm subscriptionWithName:@"query"]);
  1747. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, subscription2.state);
  1748. }
  1749. - (void)testSortAndFilterSubscriptions {
  1750. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1751. NSDate *now = NSDate.date;
  1752. [self waitForKeyPath:@"state" object:[[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query 1"]
  1753. value:@(RLMSyncSubscriptionStateComplete)];
  1754. [self waitForKeyPath:@"state" object:[[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query 2"]
  1755. value:@(RLMSyncSubscriptionStateComplete)];
  1756. [self waitForKeyPath:@"state" object:[[PartialSyncObjectB allObjectsInRealm:realm] subscribeWithName:@"query 3"]
  1757. value:@(RLMSyncSubscriptionStateComplete)];
  1758. RLMResults *unsupportedQuery = [PersonObject objectsInRealm:realm where:@"SUBQUERY(parents, $p1, $p1.age < 31 AND SUBQUERY($p1.parents, $p2, $p2.age > 35 AND $p2.name == 'Michael').@count > 0).@count > 0"];
  1759. [self waitForKeyPath:@"state" object:[unsupportedQuery subscribeWithName:@"query 4"]
  1760. value:@(RLMSyncSubscriptionStateError)];
  1761. auto subscriptions = realm.subscriptions;
  1762. XCTAssertEqual(4U, subscriptions.count);
  1763. XCTAssertEqual(0U, ([subscriptions objectsWhere:@"name = %@", @"query 0"].count));
  1764. XCTAssertEqualObjects(@"query 1", ([subscriptions objectsWhere:@"name = %@", @"query 1"].firstObject.name));
  1765. XCTAssertEqual(3U, ([subscriptions objectsWhere:@"status = %@", @(RLMSyncSubscriptionStateComplete)].count));
  1766. XCTAssertEqual(1U, ([subscriptions objectsWhere:@"status = %@", @(RLMSyncSubscriptionStateError)].count));
  1767. XCTAssertEqual(4U, ([subscriptions objectsWhere:@"createdAt >= %@", now]).count);
  1768. XCTAssertEqual(0U, ([subscriptions objectsWhere:@"createdAt < %@", now]).count);
  1769. XCTAssertEqual(4U, [subscriptions objectsWhere:@"expiresAt = nil"].count);
  1770. XCTAssertEqual(4U, [subscriptions objectsWhere:@"timeToLive = nil"].count);
  1771. XCTAssertThrows([subscriptions sortedResultsUsingKeyPath:@"name" ascending:NO]);
  1772. XCTAssertThrows([subscriptions sortedResultsUsingDescriptors:@[]]);
  1773. XCTAssertThrows([subscriptions distinctResultsUsingKeyPaths:@[@"name"]]);
  1774. }
  1775. - (void)testIncludeLinkingObjectsErrorHandling {
  1776. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1777. RLMResults *objects = [PersonObject allObjectsInRealm:realm];
  1778. RLMSyncSubscriptionOptions *opt = [RLMSyncSubscriptionOptions new];
  1779. opt.includeLinkingObjectProperties = @[@"nonexistent"];
  1780. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1781. @"Invalid LinkingObjects inclusion from key path 'nonexistent': property 'PersonObject.nonexistent' does not exist.");
  1782. opt.includeLinkingObjectProperties = @[@"name"];
  1783. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1784. @"Invalid LinkingObjects inclusion from key path 'name': property 'PersonObject.name' is of unsupported type 'string'.");
  1785. opt.includeLinkingObjectProperties = @[@"children.name"];
  1786. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1787. @"Invalid LinkingObjects inclusion from key path 'children.name': property 'PersonObject.name' is of unsupported type 'string'.");
  1788. opt.includeLinkingObjectProperties = @[@"children"];
  1789. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1790. @"Invalid LinkingObjects inclusion from key path 'children': key path must end in a LinkingObjects property and 'PersonObject.children' is of type 'array'.");
  1791. opt.includeLinkingObjectProperties = @[@"children."];
  1792. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1793. @"Invalid LinkingObjects inclusion from key path 'children.': missing property name.");
  1794. opt.includeLinkingObjectProperties = @[@""];
  1795. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1796. @"Invalid LinkingObjects inclusion from key path '': missing property name.");
  1797. }
  1798. #pragma mark - Certificate pinning
  1799. - (void)attemptLoginWithUsername:(NSString *)userName callback:(void (^)(RLMSyncUser *, NSError *))callback {
  1800. NSURL *url = [RLMObjectServerTests secureAuthServerURL];
  1801. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:userName register:YES];
  1802. XCTestExpectation *expectation = [self expectationWithDescription:@"HTTP login"];
  1803. [RLMSyncUser logInWithCredentials:creds authServerURL:url
  1804. onCompletion:^(RLMSyncUser *user, NSError *error) {
  1805. callback(user, error);
  1806. [expectation fulfill];
  1807. }];
  1808. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  1809. }
  1810. - (void)testHTTPSLoginFailsWithoutCertificate {
  1811. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1812. XCTAssertNil(user);
  1813. XCTAssertNotNil(error);
  1814. XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
  1815. XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
  1816. }];
  1817. }
  1818. static NSURL *certificateURL(NSString *filename) {
  1819. return [NSURL fileURLWithPath:[[[@(__FILE__) stringByDeletingLastPathComponent]
  1820. stringByAppendingPathComponent:@"certificates"]
  1821. stringByAppendingPathComponent:filename]];
  1822. }
  1823. - (void)testHTTPSLoginFailsWithIncorrectCertificate {
  1824. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"not-localhost.cer")};
  1825. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1826. XCTAssertNil(user);
  1827. XCTAssertNotNil(error);
  1828. XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
  1829. XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
  1830. }];
  1831. }
  1832. - (void)testHTTPSLoginFailsWithInvalidPathToCertificate {
  1833. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"nonexistent.pem")};
  1834. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1835. XCTAssertNil(user);
  1836. XCTAssertNotNil(error);
  1837. XCTAssertEqualObjects(error.domain, NSCocoaErrorDomain);
  1838. XCTAssertEqual(error.code, NSFileReadNoSuchFileError);
  1839. }];
  1840. }
  1841. - (void)testHTTPSLoginFailsWithDifferentValidCert {
  1842. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost-other.cer")};
  1843. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1844. XCTAssertNil(user);
  1845. XCTAssertNotNil(error);
  1846. XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
  1847. XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
  1848. }];
  1849. }
  1850. - (void)testHTTPSLoginFailsWithFileThatIsNotACert {
  1851. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"../test-ros-server.js")};
  1852. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1853. XCTAssertNil(user);
  1854. XCTAssertNotNil(error);
  1855. XCTAssertEqualObjects(error.domain, NSOSStatusErrorDomain);
  1856. XCTAssertEqual(error.code, errSecUnknownFormat);
  1857. }];
  1858. }
  1859. - (void)testHTTPSLoginDoesNotUseCertificateForDifferentDomain {
  1860. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"example.com": certificateURL(@"localhost.cer")};
  1861. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1862. XCTAssertNil(user);
  1863. XCTAssertNotNil(error);
  1864. XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
  1865. XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
  1866. }];
  1867. }
  1868. - (void)testHTTPSLoginSucceedsWithValidSelfSignedCertificate {
  1869. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
  1870. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1871. XCTAssertNotNil(user);
  1872. XCTAssertNil(error);
  1873. }];
  1874. }
  1875. - (void)testConfigurationFromUserAutomaticallyUsesCert {
  1876. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
  1877. __block RLMSyncUser *user;
  1878. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *u, NSError *error) {
  1879. XCTAssertNotNil(u);
  1880. XCTAssertNil(error);
  1881. user = u;
  1882. }];
  1883. RLMRealmConfiguration *config = [user configuration];
  1884. XCTAssertEqualObjects(config.syncConfiguration.realmURL.scheme, @"realms");
  1885. XCTAssertEqualObjects(config.syncConfiguration.pinnedCertificateURL, certificateURL(@"localhost.cer"));
  1886. // Verify that we can actually open the Realm
  1887. auto realm = [self openRealmWithConfiguration:config];
  1888. NSError *error;
  1889. [self waitForUploadsForRealm:realm error:&error];
  1890. XCTAssertNil(error);
  1891. }
  1892. - (void)verifyOpenSucceeds:(RLMRealmConfiguration *)config {
  1893. auto realm = [self openRealmWithConfiguration:config];
  1894. NSError *error;
  1895. [self waitForUploadsForRealm:realm error:&error];
  1896. XCTAssertNil(error);
  1897. }
  1898. - (void)verifyOpenFails:(RLMRealmConfiguration *)config {
  1899. [self openRealmWithConfiguration:config];
  1900. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for error"];
  1901. RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, __unused RLMSyncSession *session) {
  1902. XCTAssertTrue([error.domain isEqualToString:RLMSyncErrorDomain]);
  1903. XCTAssertFalse([[error.userInfo[kRLMSyncUnderlyingErrorKey] domain] isEqualToString:RLMSyncErrorDomain]);
  1904. [expectation fulfill];
  1905. };
  1906. [self waitForExpectationsWithTimeout:20.0 handler:nil];
  1907. }
  1908. - (void)testConfigurationFromInsecureUserAutomaticallyUsesCert {
  1909. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
  1910. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1911. register:YES]
  1912. server:[RLMSyncTestCase authServerURL]];
  1913. RLMRealmConfiguration *config = [user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]];
  1914. XCTAssertEqualObjects(config.syncConfiguration.realmURL.scheme, @"realms");
  1915. XCTAssertEqualObjects(config.syncConfiguration.pinnedCertificateURL, certificateURL(@"localhost.cer"));
  1916. [self verifyOpenSucceeds:config];
  1917. }
  1918. - (void)testOpenSecureRealmWithNoCert {
  1919. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1920. register:YES]
  1921. server:[RLMSyncTestCase authServerURL]];
  1922. [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
  1923. }
  1924. - (void)testOpenSecureRealmWithIncorrectCert {
  1925. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"not-localhost.cer")};
  1926. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1927. register:YES]
  1928. server:[RLMSyncTestCase authServerURL]];
  1929. [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
  1930. }
  1931. - (void)DISABLE_testOpenSecureRealmWithMissingCertFile {
  1932. // FIXME: this currently crashes inside the sync library
  1933. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"nonexistent.pem")};
  1934. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1935. register:YES]
  1936. server:[RLMSyncTestCase authServerURL]];
  1937. [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
  1938. }
  1939. #pragma mark - Custom request headers
  1940. - (void)testLoginFailsWithoutCustomHeader {
  1941. XCTestExpectation *expectation = [self expectationWithDescription:@"register user"];
  1942. [RLMSyncUser logInWithCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1943. register:YES]
  1944. authServerURL:[NSURL URLWithString:@"http://127.0.0.1:9081"]
  1945. onCompletion:^(RLMSyncUser *user, NSError *error) {
  1946. XCTAssertNil(user);
  1947. XCTAssertNotNil(error);
  1948. XCTAssertEqualObjects(@400, error.userInfo[@"statusCode"]);
  1949. [expectation fulfill];
  1950. }];
  1951. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  1952. }
  1953. - (void)testLoginUsesCustomHeader {
  1954. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
  1955. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1956. register:YES]
  1957. server:[NSURL URLWithString:@"http://127.0.0.1:9081"]];
  1958. XCTAssertNotNil(user);
  1959. }
  1960. - (void)testModifyCustomHeadersAfterOpeningRealm {
  1961. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
  1962. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1963. register:YES]
  1964. server:[NSURL URLWithString:@"http://127.0.0.1:9081"]];
  1965. XCTAssertNotNil(user);
  1966. RLMSyncManager.sharedManager.customRequestHeaders = nil;
  1967. NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"realm://127.0.0.1:9081/~/%@", NSStringFromSelector(_cmd)]];
  1968. auto c = [user configurationWithURL:url fullSynchronization:true];
  1969. // Should initially fail to connect due to the missing header
  1970. XCTestExpectation *ex1 = [self expectationWithDescription:@"connection failure"];
  1971. RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, RLMSyncSession *) {
  1972. XCTAssertNotNil(error);
  1973. XCTAssertEqualObjects(@400, [error.userInfo[@"underlying_error"] userInfo][@"statusCode"]);
  1974. [ex1 fulfill];
  1975. };
  1976. RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil];
  1977. RLMSyncSession *syncSession = realm.syncSession;
  1978. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  1979. XCTAssertEqual(syncSession.connectionState, RLMSyncConnectionStateDisconnected);
  1980. // Should successfully connect once the header is set
  1981. RLMSyncManager.sharedManager.errorHandler = nil;
  1982. auto ex2 = [[XCTKVOExpectation alloc] initWithKeyPath:@"connectionState"
  1983. object:syncSession
  1984. expectedValue:@(RLMSyncConnectionStateConnected)];
  1985. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
  1986. [self waitForExpectations:@[ex2] timeout:4.0];
  1987. // Should disconnect and fail to reconnect when the wrong header is set
  1988. XCTestExpectation *ex3 = [self expectationWithDescription:@"reconnection failure"];
  1989. RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, RLMSyncSession *) {
  1990. XCTAssertNotNil(error);
  1991. XCTAssertEqualObjects(@400, [error.userInfo[@"underlying_error"] userInfo][@"statusCode"]);
  1992. [ex3 fulfill];
  1993. };
  1994. auto ex4 = [[XCTKVOExpectation alloc] initWithKeyPath:@"connectionState"
  1995. object:syncSession
  1996. expectedValue:@(RLMSyncConnectionStateDisconnected)];
  1997. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Other-Header": @"true"};
  1998. [self waitForExpectations:@[ex3, ex4] timeout:4.0];
  1999. }
  2000. @end