RLMObjectServerTests.mm 121 KB

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