RLMObjectServerTests.mm 117 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303
  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. static const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
  1256. - (void)populateDataForUser:(RLMSyncUser *)user url:(NSURL *)url {
  1257. RLMRealm *realm = [self openRealmForURL:url user:user];
  1258. [realm beginWriteTransaction];
  1259. for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
  1260. [realm addObject:[HugeSyncObject object]];
  1261. }
  1262. [realm commitWriteTransaction];
  1263. [self waitForUploadsForRealm:realm];
  1264. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1265. }
  1266. - (void)testStreamingDownloadNotifier {
  1267. NSURL *url = REALM_URL();
  1268. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd) register:self.isParent]
  1269. server:[RLMObjectServerTests authServerURL]];
  1270. if (!self.isParent) {
  1271. [self populateDataForUser:user url:url];
  1272. return;
  1273. }
  1274. __block NSInteger callCount = 0;
  1275. __block NSUInteger transferred = 0;
  1276. __block NSUInteger transferrable = 0;
  1277. __block BOOL hasBeenFulfilled = NO;
  1278. // Register a notifier.
  1279. [self openRealmForURL:url user:user];
  1280. RLMSyncSession *session = [user sessionForURL:url];
  1281. XCTAssertNotNil(session);
  1282. XCTestExpectation *ex = [self expectationWithDescription:@"streaming-download-notifier"];
  1283. id token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionDownload
  1284. mode:RLMSyncProgressModeReportIndefinitely
  1285. block:^(NSUInteger xfr, NSUInteger xfb) {
  1286. // Make sure the values are increasing, and update our stored copies.
  1287. XCTAssert(xfr >= transferred);
  1288. XCTAssert(xfb >= transferrable);
  1289. transferred = xfr;
  1290. transferrable = xfb;
  1291. callCount++;
  1292. if (transferrable > 0 && transferred >= transferrable && !hasBeenFulfilled) {
  1293. [ex fulfill];
  1294. hasBeenFulfilled = YES;
  1295. }
  1296. }];
  1297. // Wait for the child process to upload everything.
  1298. RLMRunChildAndWait();
  1299. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  1300. [token invalidate];
  1301. // The notifier should have been called at least twice: once at the beginning and at least once
  1302. // to report progress.
  1303. XCTAssert(callCount > 1);
  1304. XCTAssert(transferred >= transferrable,
  1305. @"Transferred (%@) needs to be greater than or equal to transferrable (%@)",
  1306. @(transferred), @(transferrable));
  1307. }
  1308. - (void)testStreamingUploadNotifier {
  1309. NSURL *url = REALM_URL();
  1310. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd) register:self.isParent]
  1311. server:[RLMObjectServerTests authServerURL]];
  1312. __block NSInteger callCount = 0;
  1313. __block NSUInteger transferred = 0;
  1314. __block NSUInteger transferrable = 0;
  1315. // Open the Realm
  1316. RLMRealm *realm = [self openRealmForURL:url user:user];
  1317. // Register a notifier.
  1318. RLMSyncSession *session = [user sessionForURL:url];
  1319. XCTAssertNotNil(session);
  1320. XCTestExpectation *ex = [self expectationWithDescription:@"streaming-upload-expectation"];
  1321. auto token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionUpload
  1322. mode:RLMSyncProgressModeReportIndefinitely
  1323. block:^(NSUInteger xfr, NSUInteger xfb) {
  1324. // Make sure the values are
  1325. // increasing, and update our
  1326. // stored copies.
  1327. XCTAssert(xfr >= transferred);
  1328. XCTAssert(xfb >= transferrable);
  1329. transferred = xfr;
  1330. transferrable = xfb;
  1331. callCount++;
  1332. if (transferred > 0 && transferred >= transferrable && transferrable > 1000000 * NUMBER_OF_BIG_OBJECTS) {
  1333. [ex fulfill];
  1334. }
  1335. }];
  1336. // Upload lots of data
  1337. [realm beginWriteTransaction];
  1338. for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
  1339. [realm addObject:[HugeSyncObject object]];
  1340. }
  1341. [realm commitWriteTransaction];
  1342. // Wait for upload to begin and finish
  1343. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  1344. [token invalidate];
  1345. // The notifier should have been called at least twice: once at the beginning and at least once
  1346. // to report progress.
  1347. XCTAssert(callCount > 1);
  1348. XCTAssert(transferred >= transferrable,
  1349. @"Transferred (%@) needs to be greater than or equal to transferrable (%@)",
  1350. @(transferred), @(transferrable));
  1351. }
  1352. #pragma mark - Download Realm
  1353. - (void)testDownloadRealm {
  1354. const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
  1355. NSURL *url = REALM_URL();
  1356. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd) register:self.isParent]
  1357. server:[RLMObjectServerTests authServerURL]];
  1358. if (!self.isParent) {
  1359. [self populateDataForUser:user url:url];
  1360. return;
  1361. }
  1362. // Wait for the child process to upload everything.
  1363. RLMRunChildAndWait();
  1364. XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
  1365. RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
  1366. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]);
  1367. [RLMRealm asyncOpenWithConfiguration:c
  1368. callbackQueue:dispatch_get_main_queue()
  1369. callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
  1370. XCTAssertNil(error);
  1371. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1372. [ex fulfill];
  1373. }];
  1374. NSUInteger (^fileSize)(NSString *) = ^NSUInteger(NSString *path) {
  1375. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  1376. if (attributes)
  1377. return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
  1378. return 0;
  1379. };
  1380. XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1381. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  1382. XCTAssertGreaterThan(fileSize(c.pathOnDisk), 0U);
  1383. XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1384. }
  1385. - (void)testDownloadAlreadyOpenRealm {
  1386. const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
  1387. NSURL *url = REALM_URL();
  1388. // Log in the user.
  1389. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd) register:self.isParent]
  1390. server:[RLMObjectServerTests authServerURL]];
  1391. if (!self.isParent) {
  1392. [self populateDataForUser:user url:url];
  1393. return;
  1394. }
  1395. XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
  1396. RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
  1397. XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]);
  1398. RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil];
  1399. CHECK_COUNT(0, HugeSyncObject, realm);
  1400. [realm.syncSession suspend];
  1401. // Wait for the child process to upload everything.
  1402. RLMRunChildAndWait();
  1403. auto fileSize = ^NSUInteger(NSString *path) {
  1404. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
  1405. return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
  1406. };
  1407. NSUInteger sizeBefore = fileSize(c.pathOnDisk);
  1408. XCTAssertGreaterThan(sizeBefore, 0U);
  1409. XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1410. [RLMRealm asyncOpenWithConfiguration:c
  1411. callbackQueue:dispatch_get_main_queue()
  1412. callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
  1413. XCTAssertNil(error);
  1414. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1415. [ex fulfill];
  1416. }];
  1417. [realm.syncSession resume];
  1418. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1419. XCTAssertGreaterThan(fileSize(c.pathOnDisk), sizeBefore);
  1420. XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
  1421. CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
  1422. (void)[realm configuration];
  1423. }
  1424. - (void)testDownloadCancelsOnAuthError {
  1425. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd) register:self.isParent]
  1426. server:[RLMObjectServerTests authServerURL]];
  1427. auto c = [user configurationWithURL:[NSURL URLWithString:@"realm://127.0.0.1:9080/invalid"] fullSynchronization:true];
  1428. auto ex = [self expectationWithDescription:@"async open"];
  1429. [RLMRealm asyncOpenWithConfiguration:c callbackQueue:dispatch_get_main_queue()
  1430. callback:^(RLMRealm *realm, NSError *error) {
  1431. XCTAssertNil(realm);
  1432. XCTAssertNotNil(error);
  1433. [ex fulfill];
  1434. }];
  1435. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1436. }
  1437. - (void)testCancelDownload {
  1438. NSURL *url = REALM_URL();
  1439. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd) register:self.isParent]
  1440. server:[RLMObjectServerTests authServerURL]];
  1441. if (!self.isParent) {
  1442. [self populateDataForUser:user url:url];
  1443. return;
  1444. }
  1445. // Wait for the child process to upload everything.
  1446. RLMRunChildAndWait();
  1447. // Use a serial queue for asyncOpen to ensure that the first one adds
  1448. // the completion block before the second one cancels it
  1449. RLMSetAsyncOpenQueue(dispatch_queue_create("io.realm.asyncOpen", 0));
  1450. XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
  1451. RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
  1452. [RLMRealm asyncOpenWithConfiguration:c
  1453. callbackQueue:dispatch_get_main_queue()
  1454. callback:^(RLMRealm *realm, NSError *error) {
  1455. XCTAssertNil(realm);
  1456. XCTAssertNotNil(error);
  1457. [ex fulfill];
  1458. }];
  1459. [[RLMRealm asyncOpenWithConfiguration:c
  1460. callbackQueue:dispatch_get_main_queue()
  1461. callback:^(RLMRealm *, NSError *) {
  1462. XCTFail(@"Cancelled callback got called");
  1463. }] cancel];
  1464. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1465. }
  1466. - (void)testAsyncOpenProgressNotifications {
  1467. NSURL *url = REALM_URL();
  1468. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd) register:self.isParent]
  1469. server:[RLMObjectServerTests authServerURL]];
  1470. if (!self.isParent) {
  1471. [self populateDataForUser:user url:url];
  1472. return;
  1473. }
  1474. RLMRunChildAndWait();
  1475. XCTestExpectation *ex1 = [self expectationWithDescription:@"async open"];
  1476. XCTestExpectation *ex2 = [self expectationWithDescription:@"download progress complete"];
  1477. RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
  1478. auto task = [RLMRealm asyncOpenWithConfiguration:c
  1479. callbackQueue:dispatch_get_main_queue()
  1480. callback:^(RLMRealm *realm, NSError *error) {
  1481. XCTAssertNil(error);
  1482. XCTAssertNotNil(realm);
  1483. [ex1 fulfill];
  1484. }];
  1485. [task addProgressNotificationBlock:^(NSUInteger transferredBytes, NSUInteger transferrableBytes) {
  1486. if (transferrableBytes > 0 && transferredBytes == transferrableBytes) {
  1487. [ex2 fulfill];
  1488. }
  1489. }];
  1490. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1491. }
  1492. #pragma mark - Compact on Launch
  1493. - (void)testCompactOnLaunch {
  1494. NSURL *url = REALM_URL();
  1495. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1496. register:YES]
  1497. server:[RLMObjectServerTests authServerURL]];
  1498. NSString *path;
  1499. // Create a large object and then delete it in the next transaction so that
  1500. // the file is bloated
  1501. @autoreleasepool {
  1502. auto realm = [self openRealmForURL:url user:user];
  1503. [realm beginWriteTransaction];
  1504. [realm addObject:[HugeSyncObject object]];
  1505. [realm commitWriteTransaction];
  1506. [self waitForUploadsForRealm:realm];
  1507. [realm beginWriteTransaction];
  1508. [realm deleteAllObjects];
  1509. [realm commitWriteTransaction];
  1510. [self waitForUploadsForRealm:realm];
  1511. [self waitForDownloadsForRealm:realm];
  1512. path = realm.configuration.pathOnDisk;
  1513. }
  1514. auto fileManager = NSFileManager.defaultManager;
  1515. auto initialSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue];
  1516. // Reopen the file with a shouldCompactOnLaunch block and verify that it is
  1517. // actually compacted
  1518. auto config = [user configurationWithURL:url fullSynchronization:true];
  1519. __block bool blockCalled = false;
  1520. __block NSUInteger usedSize = 0;
  1521. config.shouldCompactOnLaunch = ^(NSUInteger, NSUInteger used) {
  1522. usedSize = used;
  1523. blockCalled = true;
  1524. return YES;
  1525. };
  1526. @autoreleasepool {
  1527. [RLMRealm realmWithConfiguration:config error:nil];
  1528. }
  1529. XCTAssertTrue(blockCalled);
  1530. auto finalSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue];
  1531. XCTAssertLessThan(finalSize, initialSize);
  1532. XCTAssertLessThanOrEqual(finalSize, usedSize + 4096U);
  1533. }
  1534. #pragma mark - Offline Client Reset
  1535. - (void)testOfflineClientReset {
  1536. NSError *error;
  1537. RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
  1538. register:YES]
  1539. server:[RLMObjectServerTests authServerURL]];
  1540. NSURL *sourceFileURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"sync-1.x" withExtension:@"realm"];
  1541. NSString *fileName = [NSString stringWithFormat:@"%@.realm", [NSUUID new]];
  1542. NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
  1543. [NSFileManager.defaultManager copyItemAtURL:sourceFileURL toURL:fileURL error:&error];
  1544. XCTAssertNil(error);
  1545. if (error) {
  1546. return;
  1547. }
  1548. RLMRealmConfiguration *configuration = [user configurationWithURL:REALM_URL() fullSynchronization:true];
  1549. RLMSyncConfiguration *syncConfig = configuration.syncConfiguration;
  1550. syncConfig.customFileURL = fileURL;
  1551. configuration.syncConfiguration = syncConfig;
  1552. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error];
  1553. XCTAssertNil(realm);
  1554. XCTAssertEqualObjects(error.domain, RLMErrorDomain);
  1555. XCTAssertEqual(error.code, RLMErrorIncompatibleSyncedFile);
  1556. RLMRealmConfiguration *backupConfiguration = error.userInfo[RLMBackupRealmConfigurationErrorKey];
  1557. XCTAssertNotNil(backupConfiguration);
  1558. // Open the backup Realm with a schema subset since it was created using the schema from .NET's unit tests.
  1559. // The Person class is declared in SwiftObjectServerTests.swift.
  1560. backupConfiguration.objectClasses = @[NSClassFromString(@"Person")];
  1561. error = nil;
  1562. RLMRealm *backupRealm = [RLMRealm realmWithConfiguration:backupConfiguration error:&error];
  1563. XCTAssertNotNil(backupRealm);
  1564. XCTAssertNil(error);
  1565. RLMResults *people = [backupRealm allObjects:@"Person"];
  1566. XCTAssertEqual(people.count, 1u);
  1567. XCTAssertEqualObjects([people[0] valueForKey:@"FirstName"], @"John");
  1568. XCTAssertEqualObjects([people[0] valueForKey:@"LastName"], @"Smith");
  1569. error = nil;
  1570. realm = [RLMRealm realmWithConfiguration:configuration error:&error];
  1571. XCTAssertNotNil(realm);
  1572. XCTAssertNil(error);
  1573. }
  1574. #pragma clang diagnostic push
  1575. #pragma clang diagnostic ignored "-Wdeprecated"
  1576. - (void)testAutomaticSyncConfiguration {
  1577. NSURL *server = [RLMObjectServerTests authServerURL];
  1578. // Automatic configuration should throw when there are no logged-in users.
  1579. XCTAssertThrows([RLMSyncConfiguration automaticConfiguration]);
  1580. RLMSyncCredentials *credsA = [RLMObjectServerTests basicCredentialsWithName:@"a" register:YES];
  1581. RLMSyncUser *userA = [self logInUserForCredentials:credsA server:server];
  1582. // Now that there's a logged-in user, we should be able to retrieve the configuration.
  1583. RLMRealmConfiguration *configuration = [RLMSyncConfiguration automaticConfiguration];
  1584. XCTAssert(configuration);
  1585. @autoreleasepool {
  1586. // And open it successfully.
  1587. RLMRealm *realm = [self openRealmWithConfiguration:configuration];
  1588. [self waitForDownloadsForRealm:realm];
  1589. }
  1590. RLMSyncCredentials *credsB = [RLMObjectServerTests basicCredentialsWithName:@"b" register:YES];
  1591. RLMSyncUser *userB = [self logInUserForCredentials:credsB server:server];
  1592. // Automatic configuration should throw since there's more than one logged-in user.
  1593. XCTAssertThrows([RLMSyncConfiguration automaticConfiguration]);
  1594. // It should still be possible to explicitly retrieve an automatic configuration for a user.
  1595. RLMRealmConfiguration *configurationA = [RLMSyncConfiguration automaticConfigurationForUser:userA];
  1596. XCTAssert(configurationA);
  1597. XCTAssertEqualObjects(configuration.syncConfiguration, configurationA.syncConfiguration);
  1598. RLMRealmConfiguration *configurationB = [RLMSyncConfiguration automaticConfigurationForUser:userB];
  1599. XCTAssert(configurationB);
  1600. XCTAssertNotEqualObjects(configuration.syncConfiguration, configurationB.syncConfiguration);
  1601. [userB logOut];
  1602. // Now that we're back to a single logged-in user, we should be able to retrieve the configuration.
  1603. configuration = [RLMSyncConfiguration automaticConfiguration];
  1604. XCTAssert(configuration);
  1605. }
  1606. #pragma clang diagnostic pop
  1607. #pragma mark - Partial sync
  1608. - (void)waitForKeyPath:(NSString *)keyPath object:(id)object value:(id)value {
  1609. [self waitForExpectations:@[[[XCTKVOExpectation alloc] initWithKeyPath:keyPath object:object expectedValue:value]] timeout:20.0];
  1610. }
  1611. - (void)testPartialSync {
  1612. // Make credentials.
  1613. NSString *name = NSStringFromSelector(_cmd);
  1614. NSURL *server = [RLMObjectServerTests authServerURL];
  1615. // Log in and populate the Realm.
  1616. @autoreleasepool {
  1617. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:YES];
  1618. RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
  1619. RLMRealmConfiguration *configuration = [user configuration];
  1620. RLMRealm *realm = [self openRealmWithConfiguration:configuration];
  1621. [realm beginWriteTransaction];
  1622. // FIXME: make this less hideous
  1623. // Add ten of each object
  1624. [realm addObject:[PartialSyncObjectA objectWithNumber:0 string:@"realm"]];
  1625. [realm addObject:[PartialSyncObjectA objectWithNumber:1 string:@""]];
  1626. [realm addObject:[PartialSyncObjectA objectWithNumber:2 string:@""]];
  1627. [realm addObject:[PartialSyncObjectA objectWithNumber:3 string:@""]];
  1628. [realm addObject:[PartialSyncObjectA objectWithNumber:4 string:@"realm"]];
  1629. [realm addObject:[PartialSyncObjectA objectWithNumber:5 string:@"sync"]];
  1630. [realm addObject:[PartialSyncObjectA objectWithNumber:6 string:@"partial"]];
  1631. [realm addObject:[PartialSyncObjectA objectWithNumber:7 string:@"partial"]];
  1632. [realm addObject:[PartialSyncObjectA objectWithNumber:8 string:@"partial"]];
  1633. [realm addObject:[PartialSyncObjectA objectWithNumber:9 string:@"partial"]];
  1634. [realm addObject:[PartialSyncObjectB objectWithNumber:0 firstString:@"" secondString:@""]];
  1635. [realm addObject:[PartialSyncObjectB objectWithNumber:1 firstString:@"" secondString:@""]];
  1636. [realm addObject:[PartialSyncObjectB objectWithNumber:2 firstString:@"" secondString:@""]];
  1637. [realm addObject:[PartialSyncObjectB objectWithNumber:3 firstString:@"" secondString:@""]];
  1638. [realm addObject:[PartialSyncObjectB objectWithNumber:4 firstString:@"" secondString:@""]];
  1639. [realm addObject:[PartialSyncObjectB objectWithNumber:5 firstString:@"" secondString:@""]];
  1640. [realm addObject:[PartialSyncObjectB objectWithNumber:6 firstString:@"" secondString:@""]];
  1641. [realm addObject:[PartialSyncObjectB objectWithNumber:7 firstString:@"" secondString:@""]];
  1642. [realm addObject:[PartialSyncObjectB objectWithNumber:8 firstString:@"" secondString:@""]];
  1643. [realm addObject:[PartialSyncObjectB objectWithNumber:9 firstString:@"" secondString:@""]];
  1644. [realm commitWriteTransaction];
  1645. [self waitForUploadsForRealm:realm];
  1646. }
  1647. // Log back in and do partial sync stuff.
  1648. @autoreleasepool {
  1649. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:NO];
  1650. RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
  1651. RLMRealmConfiguration *configuration = [user configuration];
  1652. RLMRealm *realm = [self openRealmWithConfiguration:configuration];
  1653. // Perform some partial sync queries
  1654. RLMResults *objects = [PartialSyncObjectA objectsInRealm:realm where:@"number > 5"];
  1655. RLMSyncSubscription *subscription = [objects subscribeWithName:@"query"];
  1656. // Wait for the results to become available.
  1657. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
  1658. // Verify that we got what we're looking for
  1659. XCTAssertEqual(objects.count, 4U);
  1660. for (PartialSyncObjectA *object in objects) {
  1661. XCTAssertGreaterThan(object.number, 5);
  1662. XCTAssertEqualObjects(object.string, @"partial");
  1663. }
  1664. // Verify that we didn't get any other objects
  1665. XCTAssertEqual([PartialSyncObjectA allObjectsInRealm:realm].count, objects.count);
  1666. XCTAssertEqual([PartialSyncObjectB allObjectsInRealm:realm].count, 0u);
  1667. // Create a subscription with the same name but a different query. This should trigger an error.
  1668. RLMResults *objects2 = [PartialSyncObjectA objectsInRealm:realm where:@"number < 5"];
  1669. RLMSyncSubscription *subscription2 = [objects2 subscribeWithName:@"query"];
  1670. // Wait for the error to be reported.
  1671. [self waitForKeyPath:@"state" object:subscription2 value:@(RLMSyncSubscriptionStateError)];
  1672. XCTAssertNotNil(subscription2.error);
  1673. // Unsubscribe from the query, and ensure that it correctly transitions to the invalidated state.
  1674. [subscription unsubscribe];
  1675. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateInvalidated)];
  1676. }
  1677. }
  1678. - (RLMRealm *)partialRealmWithName:(SEL)sel {
  1679. NSString *name = NSStringFromSelector(sel);
  1680. NSURL *server = [RLMObjectServerTests authServerURL];
  1681. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:YES];
  1682. RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
  1683. RLMRealmConfiguration *configuration = [user configuration];
  1684. return [self openRealmWithConfiguration:configuration];
  1685. }
  1686. - (void)testAllSubscriptionsReportsNewlyCreatedSubscription {
  1687. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1688. XCTAssertEqual(0U, realm.subscriptions.count);
  1689. RLMSyncSubscription *subscription = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 5"]
  1690. subscribeWithName:@"query"];
  1691. // Should still be 0 because the subscription is created asynchronously
  1692. XCTAssertEqual(0U, realm.subscriptions.count);
  1693. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
  1694. XCTAssertEqual(1U, realm.subscriptions.count);
  1695. RLMSyncSubscription *subscription2 = realm.subscriptions.firstObject;
  1696. XCTAssertEqualObjects(@"query", subscription2.name);
  1697. XCTAssertEqual(RLMSyncSubscriptionStateComplete, subscription2.state);
  1698. XCTAssertNil(subscription2.error);
  1699. }
  1700. - (void)testAllSubscriptionsDoesNotReportLocalError {
  1701. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1702. RLMSyncSubscription *subscription1 = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 5"]
  1703. subscribeWithName:@"query"];
  1704. [self waitForKeyPath:@"state" object:subscription1 value:@(RLMSyncSubscriptionStateComplete)];
  1705. RLMSyncSubscription *subscription2 = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 6"]
  1706. subscribeWithName:@"query"];
  1707. [self waitForKeyPath:@"state" object:subscription2 value:@(RLMSyncSubscriptionStateError)];
  1708. XCTAssertEqual(1U, realm.subscriptions.count);
  1709. }
  1710. - (void)testAllSubscriptionsReportsServerError {
  1711. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1712. 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"]
  1713. subscribeWithName:@"query"];
  1714. XCTAssertEqual(0U, realm.subscriptions.count);
  1715. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateError)];
  1716. XCTAssertEqual(1U, realm.subscriptions.count);
  1717. RLMSyncSubscription *subscription2 = realm.subscriptions.lastObject;
  1718. XCTAssertEqualObjects(@"query", subscription2.name);
  1719. XCTAssertEqual(RLMSyncSubscriptionStateError, subscription2.state);
  1720. XCTAssertNotNil(subscription2.error);
  1721. }
  1722. - (void)testUnsubscribeUsingOriginalSubscriptionObservingFetched {
  1723. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1724. RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1725. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
  1726. XCTAssertEqual(1U, realm.subscriptions.count);
  1727. RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
  1728. [original unsubscribe];
  1729. [self waitForKeyPath:@"state" object:fetched value:@(RLMSyncSubscriptionStateInvalidated)];
  1730. XCTAssertEqual(0U, realm.subscriptions.count);
  1731. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, original.state);
  1732. // XCTKVOExpecatation retains the object and releases it sometime later on
  1733. // a background thread, which causes issues if the realm is closed after
  1734. // we reset the global state
  1735. realm->_realm->close();
  1736. }
  1737. - (void)testUnsubscribeUsingFetchedSubscriptionObservingFetched {
  1738. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1739. RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1740. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
  1741. XCTAssertEqual(1U, realm.subscriptions.count);
  1742. RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
  1743. [fetched unsubscribe];
  1744. [self waitForKeyPath:@"state" object:fetched value:@(RLMSyncSubscriptionStateInvalidated)];
  1745. XCTAssertEqual(0U, realm.subscriptions.count);
  1746. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, original.state);
  1747. // XCTKVOExpecatation retains the object and releases it sometime later on
  1748. // a background thread, which causes issues if the realm is closed after
  1749. // we reset the global state
  1750. realm->_realm->close();
  1751. }
  1752. - (void)testUnsubscribeUsingFetchedSubscriptionObservingOriginal {
  1753. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1754. RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1755. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
  1756. XCTAssertEqual(1U, realm.subscriptions.count);
  1757. RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
  1758. [fetched unsubscribe];
  1759. [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateInvalidated)];
  1760. XCTAssertEqual(0U, realm.subscriptions.count);
  1761. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, fetched.state);
  1762. }
  1763. - (void)testSubscriptionWithName {
  1764. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1765. XCTAssertNil([realm subscriptionWithName:@"query"]);
  1766. RLMSyncSubscription *subscription = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
  1767. XCTAssertNil([realm subscriptionWithName:@"query"]);
  1768. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
  1769. XCTAssertNotNil([realm subscriptionWithName:@"query"]);
  1770. XCTAssertNil([realm subscriptionWithName:@"query2"]);
  1771. RLMSyncSubscription *subscription2 = [realm subscriptionWithName:@"query"];
  1772. XCTAssertEqualObjects(@"query", subscription2.name);
  1773. XCTAssertEqual(RLMSyncSubscriptionStateComplete, subscription2.state);
  1774. XCTAssertNil(subscription2.error);
  1775. [subscription unsubscribe];
  1776. XCTAssertNotNil([realm subscriptionWithName:@"query"]);
  1777. [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateInvalidated)];
  1778. XCTAssertNil([realm subscriptionWithName:@"query"]);
  1779. XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, subscription2.state);
  1780. }
  1781. - (void)testSortAndFilterSubscriptions {
  1782. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1783. NSDate *now = NSDate.date;
  1784. [self waitForKeyPath:@"state" object:[[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query 1"]
  1785. value:@(RLMSyncSubscriptionStateComplete)];
  1786. [self waitForKeyPath:@"state" object:[[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query 2"]
  1787. value:@(RLMSyncSubscriptionStateComplete)];
  1788. [self waitForKeyPath:@"state" object:[[PartialSyncObjectB allObjectsInRealm:realm] subscribeWithName:@"query 3"]
  1789. value:@(RLMSyncSubscriptionStateComplete)];
  1790. 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"];
  1791. [self waitForKeyPath:@"state" object:[unsupportedQuery subscribeWithName:@"query 4"]
  1792. value:@(RLMSyncSubscriptionStateError)];
  1793. auto subscriptions = realm.subscriptions;
  1794. XCTAssertEqual(4U, subscriptions.count);
  1795. XCTAssertEqual(0U, ([subscriptions objectsWhere:@"name = %@", @"query 0"].count));
  1796. XCTAssertEqualObjects(@"query 1", ([subscriptions objectsWhere:@"name = %@", @"query 1"].firstObject.name));
  1797. XCTAssertEqual(3U, ([subscriptions objectsWhere:@"status = %@", @(RLMSyncSubscriptionStateComplete)].count));
  1798. XCTAssertEqual(1U, ([subscriptions objectsWhere:@"status = %@", @(RLMSyncSubscriptionStateError)].count));
  1799. XCTAssertEqual(4U, ([subscriptions objectsWhere:@"createdAt >= %@", now]).count);
  1800. XCTAssertEqual(0U, ([subscriptions objectsWhere:@"createdAt < %@", now]).count);
  1801. XCTAssertEqual(4U, [subscriptions objectsWhere:@"expiresAt = nil"].count);
  1802. XCTAssertEqual(4U, [subscriptions objectsWhere:@"timeToLive = nil"].count);
  1803. XCTAssertThrows([subscriptions sortedResultsUsingKeyPath:@"name" ascending:NO]);
  1804. XCTAssertThrows([subscriptions sortedResultsUsingDescriptors:@[]]);
  1805. XCTAssertThrows([subscriptions distinctResultsUsingKeyPaths:@[@"name"]]);
  1806. }
  1807. - (void)testIncludeLinkingObjectsErrorHandling {
  1808. RLMRealm *realm = [self partialRealmWithName:_cmd];
  1809. RLMResults *objects = [PersonObject allObjectsInRealm:realm];
  1810. RLMSyncSubscriptionOptions *opt = [RLMSyncSubscriptionOptions new];
  1811. opt.includeLinkingObjectProperties = @[@"nonexistent"];
  1812. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1813. @"Invalid LinkingObjects inclusion from key path 'nonexistent': property 'PersonObject.nonexistent' does not exist.");
  1814. opt.includeLinkingObjectProperties = @[@"name"];
  1815. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1816. @"Invalid LinkingObjects inclusion from key path 'name': property 'PersonObject.name' is of unsupported type 'string'.");
  1817. opt.includeLinkingObjectProperties = @[@"children.name"];
  1818. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1819. @"Invalid LinkingObjects inclusion from key path 'children.name': property 'PersonObject.name' is of unsupported type 'string'.");
  1820. opt.includeLinkingObjectProperties = @[@"children"];
  1821. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1822. @"Invalid LinkingObjects inclusion from key path 'children': key path must end in a LinkingObjects property and 'PersonObject.children' is of type 'array'.");
  1823. opt.includeLinkingObjectProperties = @[@"children."];
  1824. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1825. @"Invalid LinkingObjects inclusion from key path 'children.': missing property name.");
  1826. opt.includeLinkingObjectProperties = @[@""];
  1827. RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
  1828. @"Invalid LinkingObjects inclusion from key path '': missing property name.");
  1829. }
  1830. #pragma mark - Certificate pinning
  1831. - (void)attemptLoginWithUsername:(NSString *)userName callback:(void (^)(RLMSyncUser *, NSError *))callback {
  1832. NSURL *url = [RLMObjectServerTests secureAuthServerURL];
  1833. RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:userName register:YES];
  1834. XCTestExpectation *expectation = [self expectationWithDescription:@"HTTP login"];
  1835. [RLMSyncUser logInWithCredentials:creds authServerURL:url
  1836. onCompletion:^(RLMSyncUser *user, NSError *error) {
  1837. callback(user, error);
  1838. [expectation fulfill];
  1839. }];
  1840. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  1841. }
  1842. - (void)testHTTPSLoginFailsWithoutCertificate {
  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. static NSURL *certificateURL(NSString *filename) {
  1851. return [NSURL fileURLWithPath:[[[@(__FILE__) stringByDeletingLastPathComponent]
  1852. stringByAppendingPathComponent:@"certificates"]
  1853. stringByAppendingPathComponent:filename]];
  1854. }
  1855. - (void)testHTTPSLoginFailsWithIncorrectCertificate {
  1856. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"not-localhost.cer")};
  1857. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1858. XCTAssertNil(user);
  1859. XCTAssertNotNil(error);
  1860. XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
  1861. XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
  1862. }];
  1863. }
  1864. - (void)testHTTPSLoginFailsWithInvalidPathToCertificate {
  1865. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"nonexistent.pem")};
  1866. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1867. XCTAssertNil(user);
  1868. XCTAssertNotNil(error);
  1869. XCTAssertEqualObjects(error.domain, NSCocoaErrorDomain);
  1870. XCTAssertEqual(error.code, NSFileReadNoSuchFileError);
  1871. }];
  1872. }
  1873. - (void)testHTTPSLoginFailsWithDifferentValidCert {
  1874. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost-other.cer")};
  1875. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1876. XCTAssertNil(user);
  1877. XCTAssertNotNil(error);
  1878. XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
  1879. XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
  1880. }];
  1881. }
  1882. - (void)testHTTPSLoginFailsWithFileThatIsNotACert {
  1883. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"../test-ros-server.js")};
  1884. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1885. XCTAssertNil(user);
  1886. XCTAssertNotNil(error);
  1887. XCTAssertEqualObjects(error.domain, NSOSStatusErrorDomain);
  1888. XCTAssertEqual(error.code, errSecUnknownFormat);
  1889. }];
  1890. }
  1891. - (void)testHTTPSLoginDoesNotUseCertificateForDifferentDomain {
  1892. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"example.com": certificateURL(@"localhost.cer")};
  1893. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1894. XCTAssertNil(user);
  1895. XCTAssertNotNil(error);
  1896. XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
  1897. XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
  1898. }];
  1899. }
  1900. - (void)testHTTPSLoginSucceedsWithValidSelfSignedCertificate {
  1901. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
  1902. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
  1903. XCTAssertNotNil(user);
  1904. XCTAssertNil(error);
  1905. }];
  1906. }
  1907. - (void)testConfigurationFromUserAutomaticallyUsesCert {
  1908. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
  1909. __block RLMSyncUser *user;
  1910. [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *u, NSError *error) {
  1911. XCTAssertNotNil(u);
  1912. XCTAssertNil(error);
  1913. user = u;
  1914. }];
  1915. RLMRealmConfiguration *config = [user configuration];
  1916. XCTAssertEqualObjects(config.syncConfiguration.realmURL.scheme, @"realms");
  1917. XCTAssertEqualObjects(config.syncConfiguration.pinnedCertificateURL, certificateURL(@"localhost.cer"));
  1918. // Verify that we can actually open the Realm
  1919. auto realm = [self openRealmWithConfiguration:config];
  1920. NSError *error;
  1921. [self waitForUploadsForRealm:realm error:&error];
  1922. XCTAssertNil(error);
  1923. }
  1924. - (void)verifyOpenSucceeds:(RLMRealmConfiguration *)config {
  1925. auto realm = [self openRealmWithConfiguration:config];
  1926. NSError *error;
  1927. [self waitForUploadsForRealm:realm error:&error];
  1928. XCTAssertNil(error);
  1929. }
  1930. - (void)verifyOpenFails:(RLMRealmConfiguration *)config {
  1931. [self openRealmWithConfiguration:config];
  1932. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for error"];
  1933. RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, __unused RLMSyncSession *session) {
  1934. XCTAssertTrue([error.domain isEqualToString:RLMSyncErrorDomain]);
  1935. XCTAssertFalse([[error.userInfo[kRLMSyncUnderlyingErrorKey] domain] isEqualToString:RLMSyncErrorDomain]);
  1936. [expectation fulfill];
  1937. };
  1938. [self waitForExpectationsWithTimeout:20.0 handler:nil];
  1939. }
  1940. - (void)testConfigurationFromInsecureUserAutomaticallyUsesCert {
  1941. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
  1942. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1943. register:YES]
  1944. server:[RLMSyncTestCase authServerURL]];
  1945. RLMRealmConfiguration *config = [user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]];
  1946. XCTAssertEqualObjects(config.syncConfiguration.realmURL.scheme, @"realms");
  1947. XCTAssertEqualObjects(config.syncConfiguration.pinnedCertificateURL, certificateURL(@"localhost.cer"));
  1948. [self verifyOpenSucceeds:config];
  1949. }
  1950. - (void)testOpenSecureRealmWithNoCert {
  1951. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1952. register:YES]
  1953. server:[RLMSyncTestCase authServerURL]];
  1954. [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
  1955. }
  1956. - (void)testOpenSecureRealmWithIncorrectCert {
  1957. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"not-localhost.cer")};
  1958. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1959. register:YES]
  1960. server:[RLMSyncTestCase authServerURL]];
  1961. [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
  1962. }
  1963. - (void)DISABLE_testOpenSecureRealmWithMissingCertFile {
  1964. // FIXME: this currently crashes inside the sync library
  1965. RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"nonexistent.pem")};
  1966. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1967. register:YES]
  1968. server:[RLMSyncTestCase authServerURL]];
  1969. [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
  1970. }
  1971. #pragma mark - Custom request headers
  1972. - (void)testLoginFailsWithoutCustomHeader {
  1973. XCTestExpectation *expectation = [self expectationWithDescription:@"register user"];
  1974. [RLMSyncUser logInWithCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1975. register:YES]
  1976. authServerURL:[NSURL URLWithString:@"http://127.0.0.1:9081"]
  1977. onCompletion:^(RLMSyncUser *user, NSError *error) {
  1978. XCTAssertNil(user);
  1979. XCTAssertNotNil(error);
  1980. XCTAssertEqualObjects(@400, error.userInfo[@"statusCode"]);
  1981. [expectation fulfill];
  1982. }];
  1983. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  1984. }
  1985. - (void)testLoginUsesCustomHeader {
  1986. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
  1987. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1988. register:YES]
  1989. server:[NSURL URLWithString:@"http://127.0.0.1:9081"]];
  1990. XCTAssertNotNil(user);
  1991. }
  1992. - (void)testModifyCustomHeadersAfterOpeningRealm {
  1993. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
  1994. RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
  1995. register:YES]
  1996. server:[NSURL URLWithString:@"http://127.0.0.1:9081"]];
  1997. XCTAssertNotNil(user);
  1998. RLMSyncManager.sharedManager.customRequestHeaders = nil;
  1999. NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"realm://127.0.0.1:9081/~/%@", NSStringFromSelector(_cmd)]];
  2000. auto c = [user configurationWithURL:url fullSynchronization:true];
  2001. // Should initially fail to connect due to the missing header
  2002. XCTestExpectation *ex1 = [self expectationWithDescription:@"connection failure"];
  2003. RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, RLMSyncSession *) {
  2004. XCTAssertNotNil(error);
  2005. XCTAssertEqualObjects(@400, [error.userInfo[@"underlying_error"] userInfo][@"statusCode"]);
  2006. [ex1 fulfill];
  2007. };
  2008. RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil];
  2009. RLMSyncSession *syncSession = realm.syncSession;
  2010. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  2011. XCTAssertEqual(syncSession.connectionState, RLMSyncConnectionStateDisconnected);
  2012. // Should successfully connect once the header is set
  2013. RLMSyncManager.sharedManager.errorHandler = nil;
  2014. auto ex2 = [[XCTKVOExpectation alloc] initWithKeyPath:@"connectionState"
  2015. object:syncSession
  2016. expectedValue:@(RLMSyncConnectionStateConnected)];
  2017. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
  2018. [self waitForExpectations:@[ex2] timeout:4.0];
  2019. // Should disconnect and fail to reconnect when the wrong header is set
  2020. XCTestExpectation *ex3 = [self expectationWithDescription:@"reconnection failure"];
  2021. RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, RLMSyncSession *) {
  2022. XCTAssertNotNil(error);
  2023. XCTAssertEqualObjects(@400, [error.userInfo[@"underlying_error"] userInfo][@"statusCode"]);
  2024. [ex3 fulfill];
  2025. };
  2026. auto ex4 = [[XCTKVOExpectation alloc] initWithKeyPath:@"connectionState"
  2027. object:syncSession
  2028. expectedValue:@(RLMSyncConnectionStateDisconnected)];
  2029. RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Other-Header": @"true"};
  2030. [self waitForExpectations:@[ex3, ex4] timeout:4.0];
  2031. }
  2032. @end