123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2016 Realm Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- //
- ////////////////////////////////////////////////////////////////////////////
- #import "RLMSyncTestCase.h"
- #import "RLMTestUtils.h"
- #import "RLMSyncSessionRefreshHandle+ObjectServerTests.h"
- #import "RLMSyncUser+ObjectServerTests.h"
- #import "RLMSyncUtil_Private.h"
- #import "RLMRealm+Sync.h"
- #import "RLMRealmConfiguration_Private.h"
- #import "RLMRealmUtil.hpp"
- #import "RLMRealm_Dynamic.h"
- #pragma mark - Test objects
- @interface PartialSyncObjectA : RLMObject
- @property NSInteger number;
- @property NSString *string;
- + (instancetype)objectWithNumber:(NSInteger)number string:(NSString *)string;
- @end
- @interface PartialSyncObjectB : RLMObject
- @property NSInteger number;
- @property NSString *firstString;
- @property NSString *secondString;
- + (instancetype)objectWithNumber:(NSInteger)number firstString:(NSString *)first secondString:(NSString *)second;
- @end
- @implementation PartialSyncObjectA
- + (instancetype)objectWithNumber:(NSInteger)number string:(NSString *)string {
- PartialSyncObjectA *object = [[PartialSyncObjectA alloc] init];
- object.number = number;
- object.string = string;
- return object;
- }
- @end
- @implementation PartialSyncObjectB
- + (instancetype)objectWithNumber:(NSInteger)number firstString:(NSString *)first secondString:(NSString *)second {
- PartialSyncObjectB *object = [[PartialSyncObjectB alloc] init];
- object.number = number;
- object.firstString = first;
- object.secondString = second;
- return object;
- }
- @end
- @implementation PersonObject
- + (NSDictionary *)linkingObjectsProperties {
- return @{@"parents": [RLMPropertyDescriptor descriptorWithClass:PersonObject.class propertyName:@"children"]};
- }
- @end
- @interface RLMObjectServerTests : RLMSyncTestCase
- @end
- @implementation RLMObjectServerTests
- #pragma mark - Authentication and Tokens
- /// Valid username/password credentials should be able to log in a user. Using the same credentials should return the
- /// same user object.
- - (void)testUsernamePasswordAuthentication {
- RLMSyncUser *firstUser = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMSyncTestCase authServerURL]];
- RLMSyncUser *secondUser = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMSyncTestCase authServerURL]];
- // Two users created with the same credential should resolve to the same actual user.
- XCTAssertTrue([firstUser.identity isEqualToString:secondUser.identity]);
- // Authentication server property should be properly set.
- XCTAssertEqualObjects(firstUser.authenticationServer, [RLMSyncTestCase authServerURL]);
- XCTAssertFalse(firstUser.isAdmin);
- }
- /// A valid admin token should be able to log in a user.
- - (void)testAdminTokenAuthentication {
- RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithAccessToken:self.adminToken identity:@"test"];
- XCTAssertNotNil(credentials);
- RLMSyncUser *user = [self logInUserForCredentials:credentials server:[RLMObjectServerTests authServerURL]];
- XCTAssertTrue(user.isAdmin);
- }
- /// An invalid username/password credential should not be able to log in a user and a corresponding error should be generated.
- - (void)testInvalidPasswordAuthentication {
- [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd) register:YES]
- server:[RLMSyncTestCase authServerURL]];
- RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithUsername:NSStringFromSelector(_cmd)
- password:@"INVALID_PASSWORD"
- register:NO];
- XCTestExpectation *expectation = [self expectationWithDescription:@""];
- [RLMSyncUser logInWithCredentials:credentials
- authServerURL:[RLMObjectServerTests authServerURL]
- onCompletion:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
- XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
- XCTAssertNotNil(error.localizedDescription);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- /// A non-existsing user should not be able to log in and a corresponding error should be generated.
- - (void)testNonExistingUsernameAuthentication {
- RLMSyncCredentials *credentials = [RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO];
- XCTestExpectation *expectation = [self expectationWithDescription:@""];
- [RLMSyncUser logInWithCredentials:credentials
- authServerURL:[RLMObjectServerTests authServerURL]
- onCompletion:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
- XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
- XCTAssertNotNil(error.localizedDescription);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- /// Registering a user with existing username should return corresponding error.
- - (void)testExistingUsernameRegistration {
- RLMSyncCredentials *credentials = [RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES];
- [self logInUserForCredentials:credentials server:[RLMSyncTestCase authServerURL]];
- XCTestExpectation *expectation = [self expectationWithDescription:@""];
- [RLMSyncUser logInWithCredentials:credentials
- authServerURL:[RLMObjectServerTests authServerURL]
- onCompletion:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
- XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
- XCTAssertNotNil(error.localizedDescription);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- /// Errors reported in RLMSyncManager.errorHandler shouldn't contain sync error domain errors as underlying error
- - (void)testSyncErrorHandlerErrorDomain {
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- XCTAssertNotNil(user);
- NSURL *realmURL = [NSURL URLWithString:@"realm://127.0.0.1:9080/THE_PATH_USER_DONT_HAVE_ACCESS_TO/test"];
- RLMRealmConfiguration *c = [user configurationWithURL:realmURL fullSynchronization:true];
- NSError *error = nil;
- __attribute__((objc_precise_lifetime)) RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:&error];
- XCTAssertNil(error);
- XCTAssertTrue(realm.isEmpty);
- XCTestExpectation *expectation = [self expectationWithDescription:@""];
- [RLMSyncManager sharedManager].errorHandler = ^(__unused NSError *error,
- __unused RLMSyncSession *session) {
- XCTAssertTrue([error.domain isEqualToString:RLMSyncErrorDomain]);
- XCTAssertFalse([[error.userInfo[kRLMSyncUnderlyingErrorKey] domain] isEqualToString:RLMSyncErrorDomain]);
- [expectation fulfill];
- };
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- /// The pre-emptive token refresh subsystem should function, and properly refresh the token.
- - (void)testPreemptiveTokenRefresh {
- // Prepare the test.
- __block NSInteger refreshCount = 0;
- __block NSInteger errorCount = 0;
- [RLMSyncManager sharedManager].errorHandler = ^(__unused NSError *error,
- __unused RLMSyncSession *session) {
- errorCount++;
- };
- __block XCTestExpectation *ex;
- [RLMSyncSessionRefreshHandle calculateFireDateUsingTestLogic:YES
- blockOnRefreshCompletion:^(BOOL success) {
- XCTAssertTrue(success);
- refreshCount++;
- [ex fulfill];
- }];
- // Open the Realm.
- NSURL *url = REALM_URL();
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:true]
- server:[RLMObjectServerTests authServerURL]];
- __attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:user];
- ex = [self expectationWithDescription:@"Timer fired"];
- [self waitForExpectationsWithTimeout:10 handler:nil];
- XCTAssertTrue(errorCount == 0);
- XCTAssertTrue(refreshCount > 0);
- }
- #pragma mark - Users
- /// `[RLMSyncUser all]` should be updated once a user is logged in.
- - (void)testBasicUserPersistence {
- XCTAssertNil([RLMSyncUser currentUser]);
- XCTAssertEqual([[RLMSyncUser allUsers] count], 0U);
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- XCTAssertNotNil(user);
- XCTAssertEqual([[RLMSyncUser allUsers] count], 1U);
- XCTAssertEqualObjects([RLMSyncUser allUsers], @{user.identity: user});
- XCTAssertEqualObjects([RLMSyncUser currentUser], user);
- RLMSyncUser *user2 = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:[NSStringFromSelector(_cmd) stringByAppendingString:@"2"]
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- XCTAssertEqual([[RLMSyncUser allUsers] count], 2U);
- NSDictionary *dict2 = @{user.identity: user, user2.identity: user2};
- XCTAssertEqualObjects([RLMSyncUser allUsers], dict2);
- RLMAssertThrowsWithReasonMatching([RLMSyncUser currentUser], @"currentUser cannot be called if more that one valid, logged-in user exists");
- }
- /// `[RLMSyncUser currentUser]` should become nil if the user is logged out.
- - (void)testCurrentUserLogout {
- XCTAssertNil([RLMSyncUser currentUser]);
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- XCTAssertNotNil(user);
- XCTAssertEqualObjects([RLMSyncUser currentUser], user);
- [user logOut];
- XCTAssertNil([RLMSyncUser currentUser]);
- }
- /// A sync user should return a session when asked for it based on the path.
- - (void)testUserGetSessionForValidURL {
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- NSURL *url = REALM_URL();
- [self openRealmForURL:url user:user immediatelyBlock:^{
- RLMSyncSession *session = [user sessionForURL:url];
- XCTAssertNotNil(session);
- }];
- // Check session existence after binding.
- RLMSyncSession *session = [user sessionForURL:url];
- XCTAssertNotNil(session);
- }
- /// A sync user should return nil when asked for a URL that doesn't exist.
- - (void)testUserGetSessionForInvalidURL {
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- RLMSyncSession *badSession = [user sessionForURL:[NSURL URLWithString:@"realm://127.0.0.1:9080/noSuchRealm"]];
- XCTAssertNil(badSession);
- }
- /// A sync user should be able to successfully change their own password.
- - (void)testUserChangePassword {
- NSString *userName = NSStringFromSelector(_cmd);
- NSString *firstPassword = @"a";
- NSString *secondPassword = @"b";
- // Successfully create user, change its password, log out,
- // then fail to change password again due to being logged out.
- {
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:firstPassword
- register:YES];
- RLMSyncUser *user = [self logInUserForCredentials:creds
- server:[RLMObjectServerTests authServerURL]];
- XCTestExpectation *ex = [self expectationWithDescription:@"change password callback invoked"];
- [user changePassword:secondPassword completion:^(NSError * _Nullable error) {
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- [user logOut];
- ex = [self expectationWithDescription:@"change password callback invoked"];
- [user changePassword:@"fail" completion:^(NSError * _Nullable error) {
- XCTAssertNotNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- // Fail to log in with original password.
- {
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:firstPassword
- register:NO];
- XCTestExpectation *ex = [self expectationWithDescription:@"login callback invoked"];
- [RLMSyncUser logInWithCredentials:creds
- authServerURL:[RLMObjectServerTests authServerURL]
- onCompletion:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
- XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
- XCTAssertNotNil(error.localizedDescription);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- // Successfully log in with new password.
- {
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:secondPassword
- register:NO];
- RLMSyncUser *user = [self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]];
- XCTAssertNotNil(user);
- XCTAssertEqualObjects(RLMSyncUser.currentUser, user);
- [user logOut];
- XCTAssertNil(RLMSyncUser.currentUser);
- }
- }
- /// A sync admin user should be able to successfully change another user's password.
- - (void)testOtherUserChangePassword {
- // Create admin user.
- NSURL *url = [RLMObjectServerTests authServerURL];
- RLMSyncUser *adminUser = [self createAdminUserForURL:url username:[[NSUUID UUID] UUIDString]];
- NSString *username = NSStringFromSelector(_cmd);
- NSString *firstPassword = @"a";
- NSString *secondPassword = @"b";
- NSString *nonAdminUserID = nil;
- // Successfully create user.
- {
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:username
- password:firstPassword
- register:YES];
- RLMSyncUser *user = [self logInUserForCredentials:creds server:url];
- nonAdminUserID = user.identity;
- [user logOut];
- }
- // Fail to change password from non-admin user.
- {
- NSString *username2 = [NSString stringWithFormat:@"%@_2", username];
- RLMSyncCredentials *creds2 = [RLMSyncCredentials credentialsWithUsername:username2
- password:@"a"
- register:YES];
- RLMSyncUser *user2 = [self logInUserForCredentials:creds2 server:url];
- XCTestExpectation *ex = [self expectationWithDescription:@"change password callback invoked"];
- [user2 changePassword:@"foobar" forUserID:nonAdminUserID completion:^(NSError *error) {
- XCTAssertNotNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- // Change password from admin user.
- {
- XCTestExpectation *ex = [self expectationWithDescription:@"change password callback invoked"];
- [adminUser changePassword:secondPassword forUserID:nonAdminUserID completion:^(NSError *error) {
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- // Fail to log in with original password.
- {
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:username
- password:firstPassword
- register:NO];
- XCTestExpectation *ex = [self expectationWithDescription:@"login callback invoked"];
- [RLMSyncUser logInWithCredentials:creds
- authServerURL:[RLMObjectServerTests authServerURL]
- onCompletion:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqual(error.domain, RLMSyncAuthErrorDomain);
- XCTAssertEqual(error.code, RLMSyncAuthErrorInvalidCredential);
- XCTAssertNotNil(error.localizedDescription);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- // Successfully log in with new password.
- {
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:username
- password:secondPassword
- register:NO];
- RLMSyncUser *user = [self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]];
- XCTAssertNotNil(user);
- [user logOut];
- }
- }
- - (void)testRequestPasswordResetForRegisteredUser {
- NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:@"a" register:YES];
- [[self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]] logOut];
- XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser requestPasswordResetForAuthServer:[RLMObjectServerTests authServerURL] userEmail:userName completion:^(NSError *error) {
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- NSString *token = [self emailForAddress:userName];
- XCTAssertNotNil(token);
- // Use the password reset token
- ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser completePasswordResetForAuthServer:[RLMObjectServerTests authServerURL] token:token password:@"new password"
- completion:^(NSError *error) {
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- // Should now be able to log in with the new password
- {
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName
- password:@"new password"
- register:NO];
- RLMSyncUser *user = [self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]];
- XCTAssertNotNil(user);
- [user logOut];
- }
- // Reusing the token should fail
- ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser completePasswordResetForAuthServer:[RLMObjectServerTests authServerURL] token:token password:@"new password 2"
- completion:^(NSError *error) {
- XCTAssertNotNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- - (void)testRequestPasswordResetForNonexistentUser {
- NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
- XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser requestPasswordResetForAuthServer:[RLMObjectServerTests authServerURL] userEmail:userName completion:^(NSError *error) {
- // Not an error even though the user doesn't exist
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- // Should not have sent an email to the non-registered user
- XCTAssertNil([self emailForAddress:userName]);
- }
- - (void)testRequestPasswordResetWithBadAuthURL {
- NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
- XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
- NSURL *badAuthUrl = [[RLMObjectServerTests authServerURL] URLByAppendingPathComponent:@"/bad"];
- [RLMSyncUser requestPasswordResetForAuthServer:badAuthUrl userEmail:userName completion:^(NSError *error) {
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.userInfo[@"statusCode"], @404);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- - (void)testRequestConfirmEmailForRegisteredUser {
- NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
- RLMSyncCredentials *creds = [RLMSyncCredentials credentialsWithUsername:userName password:@"a" register:YES];
- [[self logInUserForCredentials:creds server:[RLMObjectServerTests authServerURL]] logOut];
- // This token is sent by ROS upon user registration
- NSString *registrationToken = [self emailForAddress:userName];
- XCTAssertNotNil(registrationToken);
- XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser requestEmailConfirmationForAuthServer:[RLMObjectServerTests authServerURL]
- userEmail:userName completion:^(NSError *error) {
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- // This token should have been created when requestEmailConfirmationForAuthServer was called
- NSString *token = [self emailForAddress:userName];
- XCTAssertNotNil(token);
- XCTAssertNotEqual(token, registrationToken);
- // Use the token
- ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser confirmEmailForAuthServer:[RLMObjectServerTests authServerURL] token:token
- completion:^(NSError *error) {
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- // Reusing the token should fail
- ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser confirmEmailForAuthServer:[RLMObjectServerTests authServerURL] token:token
- completion:^(NSError *error) {
- XCTAssertNotNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- - (void)testRequestConfirmEmailForNonexistentUser {
- NSString *userName = [NSStringFromSelector(_cmd) stringByAppendingString:@"@example.com"];
- XCTestExpectation *ex = [self expectationWithDescription:@"callback invoked"];
- [RLMSyncUser requestEmailConfirmationForAuthServer:[RLMObjectServerTests authServerURL]
- userEmail:userName completion:^(NSError *error) {
- // Not an error even though the user doesn't exist
- XCTAssertNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- // Should not have sent an email to the non-registered user
- XCTAssertNil([self emailForAddress:userName]);
- }
- /// A sync admin user should be able to retrieve information about other users.
- - (void)testRetrieveUserInfo {
- NSString *nonAdminUsername = @"meela@realm.example.org";
- NSString *adminUsername = @"jyaku";
- NSString *pw = @"p";
- NSURL *server = [RLMObjectServerTests authServerURL];
- // Create a non-admin user.
- RLMSyncCredentials *c1 = [RLMSyncCredentials credentialsWithUsername:nonAdminUsername password:pw register:YES];
- RLMSyncUser *nonAdminUser = [self logInUserForCredentials:c1 server:server];
- // Create an admin user.
- __unused RLMSyncUser *adminUser = [self createAdminUserForURL:server username:adminUsername];
- // Create another admin user.
- RLMSyncUser *userDoingLookups = [self createAdminUserForURL:server username:[[NSUUID UUID] UUIDString]];
- // Get the non-admin user's info.
- XCTestExpectation *ex1 = [self expectationWithDescription:@"should be able to get info about non-admin user"];
- [userDoingLookups retrieveInfoForUser:nonAdminUsername
- identityProvider:RLMIdentityProviderUsernamePassword
- completion:^(RLMSyncUserInfo *info, NSError *err) {
- XCTAssertNil(err);
- XCTAssertNotNil(info);
- XCTAssertGreaterThan([info.accounts count], ((NSUInteger) 0));
- RLMSyncUserAccountInfo *acctInfo = [info.accounts firstObject];
- XCTAssertEqualObjects(acctInfo.providerUserIdentity, nonAdminUsername);
- XCTAssertEqualObjects(acctInfo.provider, RLMIdentityProviderUsernamePassword);
- XCTAssertFalse(info.isAdmin);
- [ex1 fulfill];
- }];
- [self waitForExpectationsWithTimeout:10 handler:nil];
- // Get the admin user's info.
- XCTestExpectation *ex2 = [self expectationWithDescription:@"should be able to get info about admin user"];
- [userDoingLookups retrieveInfoForUser:adminUsername
- identityProvider:RLMIdentityProviderDebug
- completion:^(RLMSyncUserInfo *info, NSError *err) {
- XCTAssertNil(err);
- XCTAssertNotNil(info);
- XCTAssertGreaterThan([info.accounts count], ((NSUInteger) 0));
- RLMSyncUserAccountInfo *acctInfo = [info.accounts firstObject];
- XCTAssertEqualObjects(acctInfo.providerUserIdentity, adminUsername);
- XCTAssertEqualObjects(acctInfo.provider, RLMIdentityProviderDebug);
- XCTAssertTrue(info.isAdmin);
- [ex2 fulfill];
- }];
- [self waitForExpectationsWithTimeout:10 handler:nil];
- // Get invalid user's info.
- XCTestExpectation *ex3 = [self expectationWithDescription:@"should fail for non-existent user"];
- [userDoingLookups retrieveInfoForUser:@"invalid_user@realm.example.org"
- identityProvider:RLMIdentityProviderUsernamePassword
- completion:^(RLMSyncUserInfo *info, NSError *err) {
- XCTAssertNotNil(err);
- XCTAssertEqualObjects(err.domain, RLMSyncAuthErrorDomain);
- XCTAssertEqual(err.code, RLMSyncAuthErrorUserDoesNotExist);
- XCTAssertNil(info);
- [ex3 fulfill];
- }];
- [self waitForExpectationsWithTimeout:10 handler:nil];
- // Get info using user without admin privileges.
- XCTestExpectation *ex4 = [self expectationWithDescription:@"should fail for user without admin privileges"];
- [nonAdminUser retrieveInfoForUser:adminUsername
- identityProvider:RLMIdentityProviderUsernamePassword
- completion:^(RLMSyncUserInfo *info, NSError *err) {
- XCTAssertNotNil(err);
- XCTAssertEqualObjects(err.domain, RLMSyncAuthErrorDomain);
- // FIXME: Shouldn't this be RLMSyncAuthErrorAccessDeniedOrInvalidPath?
- XCTAssertEqual(err.code, RLMSyncAuthErrorUserDoesNotExist);
- XCTAssertNil(info);
- [ex4 fulfill];
- }];
- [self waitForExpectationsWithTimeout:10 handler:nil];
- }
- /// The login queue argument should be respected.
- - (void)testLoginQueueForSuccessfulLogin {
- // Make global queue
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- RLMSyncCredentials *c1 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
- password:@"p"
- register:YES];
- XCTestExpectation *ex1 = [self expectationWithDescription:@"User logs in successfully on background queue"];
- [RLMSyncUser logInWithCredentials:c1
- authServerURL:[RLMObjectServerTests authServerURL]
- timeout:30.0
- callbackQueue:queue
- onCompletion:^(RLMSyncUser *user, __unused NSError *error) {
- XCTAssertNotNil(user);
- XCTAssertFalse([NSThread isMainThread]);
- [ex1 fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- RLMSyncCredentials *c2 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
- password:@"p"
- register:YES];
- XCTestExpectation *ex2 = [self expectationWithDescription:@"User logs in successfully on main queue"];
- [RLMSyncUser logInWithCredentials:c2
- authServerURL:[RLMObjectServerTests authServerURL]
- timeout:30.0
- callbackQueue:dispatch_get_main_queue()
- onCompletion:^(RLMSyncUser *user, __unused NSError *error) {
- XCTAssertNotNil(user);
- XCTAssertTrue([NSThread isMainThread]);
- [ex2 fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- /// The login queue argument should be respected.
- - (void)testLoginQueueForFailedLogin {
- // Make global queue
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- RLMSyncCredentials *c1 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
- password:@"p"
- register:NO];
- XCTestExpectation *ex1 = [self expectationWithDescription:@"Error returned on background queue"];
- [RLMSyncUser logInWithCredentials:c1
- authServerURL:[RLMObjectServerTests authServerURL]
- timeout:30.0
- callbackQueue:queue
- onCompletion:^(__unused RLMSyncUser *user, NSError *error) {
- XCTAssertNotNil(error);
- XCTAssertFalse([NSThread isMainThread]);
- [ex1 fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- RLMSyncCredentials *c2 = [RLMSyncCredentials credentialsWithUsername:[[NSUUID UUID] UUIDString]
- password:@"p"
- register:NO];
- XCTestExpectation *ex2 = [self expectationWithDescription:@"Error returned on main queue"];
- [RLMSyncUser logInWithCredentials:c2
- authServerURL:[RLMObjectServerTests authServerURL]
- timeout:30.0
- callbackQueue:dispatch_get_main_queue()
- onCompletion:^(__unused RLMSyncUser *user, NSError *error) {
- XCTAssertNotNil(error);
- XCTAssertTrue([NSThread isMainThread]);
- [ex2 fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- - (void)testUserExpirationCallback {
- NSString *username = NSStringFromSelector(_cmd);
- RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithUsername:username
- password:@"a"
- register:YES];
- RLMSyncUser *user = [self logInUserForCredentials:credentials
- server:[RLMObjectServerTests authServerURL]];
- XCTestExpectation *ex = [self expectationWithDescription:@"callback should fire"];
- // Set a callback on the user
- __weak RLMSyncUser *weakUser = user;
- user.errorHandler = ^(RLMSyncUser *u, NSError *error) {
- XCTAssertEqualObjects(u.identity, weakUser.identity);
- // Make sure we get the right error.
- XCTAssertEqualObjects(error.domain, RLMSyncAuthErrorDomain);
- XCTAssertEqual(error.code, RLMSyncAuthErrorAccessDeniedOrInvalidPath);
- [ex fulfill];
- };
- // Screw up the token on the user using a debug API
- [self manuallySetRefreshTokenForUser:user value:@"not_a_real_refresh_token"];
- // Try to log in a Realm; this will cause our errorHandler block defined above to be fired.
- __attribute__((objc_precise_lifetime)) RLMRealm *r = [self immediatelyOpenRealmForURL:REALM_URL() user:user];
- [self waitForExpectationsWithTimeout:10.0 handler:nil];
- XCTAssertTrue(user.state == RLMSyncUserStateLoggedOut);
- }
- #pragma mark - Basic Sync
- /// It should be possible to successfully open a Realm configured for sync with an access token.
- - (void)testOpenRealmWithAdminToken {
- // FIXME (tests): opening a Realm with the access token, then opening a Realm at the same virtual path
- // with normal credentials, causes Realms to fail to bind with a "bad virtual path" error.
- RLMSyncCredentials *credentials = [RLMSyncCredentials credentialsWithAccessToken:self.adminToken identity:@"test"];
- XCTAssertNotNil(credentials);
- RLMSyncUser *user = [self logInUserForCredentials:credentials
- server:[RLMObjectServerTests authServerURL]];
- NSURL *url = [NSURL URLWithString:@"realm://127.0.0.1:9080/testSyncWithAdminToken"];
- RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:YES];
- NSError *error = nil;
- RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:&error];
- XCTAssertNil(error);
- XCTAssertTrue(realm.isEmpty);
- }
- /// It should be possible to successfully open a Realm configured for sync with a normal user.
- - (void)testOpenRealmWithNormalCredentials {
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- NSURL *url = REALM_URL();
- RLMRealm *realm = [self openRealmForURL:url user:user];
- XCTAssertTrue(realm.isEmpty);
- }
- /// If client B adds objects to a synced Realm, client A should see those objects.
- - (void)testAddObjects {
- NSURL *url = REALM_URL();
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- RLMRealm *realm = [self openRealmForURL:url user:user];
- if (self.isParent) {
- CHECK_COUNT(0, SyncObject, realm);
- RLMRunChildAndWait();
- [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@3]];
- } else {
- // Add objects.
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- }
- }
- /// If client B deletes objects from a synced Realm, client A should see the effects of that deletion.
- - (void)testDeleteObjects {
- NSURL *url = REALM_URL();
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- RLMRealm *realm = [self openRealmForURL:url user:user];
- if (self.isParent) {
- // Add objects.
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1", @"parent-2", @"parent-3"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- RLMRunChildAndWait();
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(0, SyncObject, realm);
- } else {
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- [realm beginWriteTransaction];
- [realm deleteAllObjects];
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(0, SyncObject, realm);
- }
- }
- #pragma mark - Encryption
- /// If client B encrypts its synced Realm, client A should be able to access that Realm with a different encryption key.
- - (void)testEncryptedSyncedRealm {
- NSURL *url = REALM_URL();
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- NSData *key = RLMGenerateKey();
- RLMRealm *realm = [self openRealmForURL:url user:user encryptionKey:key
- stopPolicy:RLMSyncStopPolicyAfterChangesUploaded immediatelyBlock:nil];
- if (self.isParent) {
- CHECK_COUNT(0, SyncObject, realm);
- RLMRunChildAndWait();
- [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@3]];
- } else {
- // Add objects.
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- }
- }
- /// If an encrypted synced Realm is re-opened with the wrong key, throw an exception.
- - (void)testEncryptedSyncedRealmWrongKey {
- NSURL *url = REALM_URL();
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- if (self.isParent) {
- NSString *path;
- @autoreleasepool {
- RLMRealm *realm = [self openRealmForURL:url user:user encryptionKey:RLMGenerateKey()
- stopPolicy:RLMSyncStopPolicyImmediately immediatelyBlock:nil];
- path = realm.configuration.pathOnDisk;
- CHECK_COUNT(0, SyncObject, realm);
- RLMRunChildAndWait();
- [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@3]];
- }
- RLMRealmConfiguration *c = [RLMRealmConfiguration defaultConfiguration];
- c.fileURL = [NSURL fileURLWithPath:path];
- RLMAssertThrowsWithError([RLMRealm realmWithConfiguration:c error:nil],
- @"Unable to open a realm at path",
- RLMErrorFileAccess,
- @"invalid mnemonic");
- c.encryptionKey = RLMGenerateKey();
- RLMAssertThrowsWithError([RLMRealm realmWithConfiguration:c error:nil],
- @"Unable to open a realm at path",
- RLMErrorFileAccess,
- @"Realm file decryption failed");
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user encryptionKey:RLMGenerateKey()
- stopPolicy:RLMSyncStopPolicyImmediately immediatelyBlock:nil];
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- }
- }
- #pragma mark - Multiple Realm Sync
- /// If a client opens multiple Realms, there should be one session object for each Realm that was opened.
- - (void)testMultipleRealmsSessions {
- NSURL *urlA = CUSTOM_REALM_URL(@"a");
- NSURL *urlB = CUSTOM_REALM_URL(@"b");
- NSURL *urlC = CUSTOM_REALM_URL(@"c");
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- // Open three Realms.
- __attribute__((objc_precise_lifetime)) RLMRealm *realmealmA = [self openRealmForURL:urlA user:user];
- __attribute__((objc_precise_lifetime)) RLMRealm *realmealmB = [self openRealmForURL:urlB user:user];
- __attribute__((objc_precise_lifetime)) RLMRealm *realmealmC = [self openRealmForURL:urlC user:user];
- // Make sure there are three active sessions for the user.
- XCTAssert(user.allSessions.count == 3, @"Expected 3 sessions, but didn't get 3 sessions");
- XCTAssertNotNil([user sessionForURL:urlA], @"Expected to get a session for URL A");
- XCTAssertNotNil([user sessionForURL:urlB], @"Expected to get a session for URL B");
- XCTAssertNotNil([user sessionForURL:urlC], @"Expected to get a session for URL C");
- XCTAssertTrue([user sessionForURL:urlA].state == RLMSyncSessionStateActive, @"Expected active session for URL A");
- XCTAssertTrue([user sessionForURL:urlB].state == RLMSyncSessionStateActive, @"Expected active session for URL B");
- XCTAssertTrue([user sessionForURL:urlC].state == RLMSyncSessionStateActive, @"Expected active session for URL C");
- }
- /// A client should be able to open multiple Realms and add objects to each of them.
- - (void)testMultipleRealmsAddObjects {
- NSURL *urlA = CUSTOM_REALM_URL(@"a");
- NSURL *urlB = CUSTOM_REALM_URL(@"b");
- NSURL *urlC = CUSTOM_REALM_URL(@"c");
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- RLMRealm *realmA = [self openRealmForURL:urlA user:user];
- RLMRealm *realmB = [self openRealmForURL:urlB user:user];
- RLMRealm *realmC = [self openRealmForURL:urlC user:user];
- if (self.isParent) {
- [self waitForDownloadsForRealm:realmA];
- [self waitForDownloadsForRealm:realmB];
- [self waitForDownloadsForRealm:realmC];
- CHECK_COUNT(0, SyncObject, realmA);
- CHECK_COUNT(0, SyncObject, realmB);
- CHECK_COUNT(0, SyncObject, realmC);
- RLMRunChildAndWait();
- [self waitForDownloadsForUser:user
- realms:@[realmA, realmB, realmC]
- realmURLs:@[urlA, urlB, urlC]
- expectedCounts:@[@3, @2, @5]];
- } else {
- // Add objects.
- [self addSyncObjectsToRealm:realmA
- descriptions:@[@"child-A1", @"child-A2", @"child-A3"]];
- [self addSyncObjectsToRealm:realmB
- descriptions:@[@"child-B1", @"child-B2"]];
- [self addSyncObjectsToRealm:realmC
- descriptions:@[@"child-C1", @"child-C2", @"child-C3", @"child-C4", @"child-C5"]];
- [self waitForUploadsForRealm:realmA];
- [self waitForUploadsForRealm:realmB];
- [self waitForUploadsForRealm:realmC];
- CHECK_COUNT(3, SyncObject, realmA);
- CHECK_COUNT(2, SyncObject, realmB);
- CHECK_COUNT(5, SyncObject, realmC);
- }
- }
- /// A client should be able to open multiple Realms and delete objects from each of them.
- - (void)testMultipleRealmsDeleteObjects {
- NSURL *urlA = CUSTOM_REALM_URL(@"a");
- NSURL *urlB = CUSTOM_REALM_URL(@"b");
- NSURL *urlC = CUSTOM_REALM_URL(@"c");
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- RLMRealm *realmA = [self openRealmForURL:urlA user:user];
- RLMRealm *realmB = [self openRealmForURL:urlB user:user];
- RLMRealm *realmC = [self openRealmForURL:urlC user:user];
- if (self.isParent) {
- [self waitForDownloadsForRealm:realmA];
- [self waitForDownloadsForRealm:realmB];
- [self waitForDownloadsForRealm:realmC];
- // Add objects.
- [self addSyncObjectsToRealm:realmA
- descriptions:@[@"parent-A1", @"parent-A2", @"parent-A3", @"parent-A4"]];
- [self addSyncObjectsToRealm:realmB
- descriptions:@[@"parent-B1", @"parent-B2", @"parent-B3", @"parent-B4", @"parent-B5"]];
- [self addSyncObjectsToRealm:realmC
- descriptions:@[@"parent-C1", @"parent-C2"]];
- [self waitForUploadsForRealm:realmA];
- [self waitForUploadsForRealm:realmB];
- [self waitForUploadsForRealm:realmC];
- CHECK_COUNT(4, SyncObject, realmA);
- CHECK_COUNT(5, SyncObject, realmB);
- CHECK_COUNT(2, SyncObject, realmC);
- RLMRunChildAndWait();
- [self waitForDownloadsForUser:user
- realms:@[realmA, realmB, realmC]
- realmURLs:@[urlA, urlB, urlC]
- expectedCounts:@[@0, @0, @0]];
- } else {
- // Delete all the objects from the Realms.
- [self waitForDownloadsForRealm:realmA];
- [self waitForDownloadsForRealm:realmB];
- [self waitForDownloadsForRealm:realmC];
- CHECK_COUNT(4, SyncObject, realmA);
- CHECK_COUNT(5, SyncObject, realmB);
- CHECK_COUNT(2, SyncObject, realmC);
- [realmA beginWriteTransaction];
- [realmA deleteAllObjects];
- [realmA commitWriteTransaction];
- [realmB beginWriteTransaction];
- [realmB deleteAllObjects];
- [realmB commitWriteTransaction];
- [realmC beginWriteTransaction];
- [realmC deleteAllObjects];
- [realmC commitWriteTransaction];
- [self waitForUploadsForRealm:realmA];
- [self waitForUploadsForRealm:realmB];
- [self waitForUploadsForRealm:realmC];
- CHECK_COUNT(0, SyncObject, realmA);
- CHECK_COUNT(0, SyncObject, realmB);
- CHECK_COUNT(0, SyncObject, realmC);
- }
- }
- #pragma mark - Session Lifetime
- /// When a session opened by a Realm goes out of scope, it should stay alive long enough to finish any waiting uploads.
- - (void)testUploadChangesWhenRealmOutOfScope {
- const NSInteger OBJECT_COUNT = 10000;
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- if (self.isParent) {
- // Open the Realm in an autorelease pool so that it is destroyed as soon as possible.
- @autoreleasepool {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- [realm beginWriteTransaction];
- for (NSInteger i=0; i<OBJECT_COUNT; i++) {
- [realm addObject:[[SyncObject alloc] initWithValue:@[[NSString stringWithFormat:@"parent-%@", @(i+1)]]]];
- }
- [realm commitWriteTransaction];
- CHECK_COUNT(OBJECT_COUNT, SyncObject, realm);
- }
- // Run the sub-test. (Give the upload a bit of time to start.)
- // NOTE: This sleep should be fine because:
- // - There is currently no API that allows asynchronous coordination for waiting for an upload to begin.
- // - A delay longer than the specified one will not affect the outcome of the test.
- sleep(2);
- RLMRunChildAndWait();
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Wait for download to complete.
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(OBJECT_COUNT, SyncObject, realm);
- }
- }
- #pragma mark - Logging Back In
- /// A Realm that was opened before a user logged out should be able to resume uploading if the user logs back in.
- - (void)testLogBackInSameRealmUpload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- RLMRealm *realm = [self openRealmForURL:url user:user];
- if (self.isParent) {
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
- CHECK_COUNT(1, SyncObject, realm);
- [self waitForUploadsForRealm:realm];
- // Log out the user.
- [user logOut];
- // Log the user back in.
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-2", @"parent-3"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- RLMRunChildAndWait();
- } else {
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- }
- }
- /// A Realm that was opened before a user logged out should be able to resume downloading if the user logs back in.
- - (void)testLogBackInSameRealmDownload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- RLMRealm *realm = [self openRealmForURL:url user:user];
- if (self.isParent) {
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
- CHECK_COUNT(1, SyncObject, realm);
- [self waitForUploadsForRealm:realm];
- // Log out the user.
- [user logOut];
- // Log the user back in.
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- RLMRunChildAndWait();
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- } else {
- [self waitForDownloadsForRealm:realm];
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- }
- }
- /// A Realm that was opened while a user was logged out should be able to start uploading if the user logs back in.
- - (void)testLogBackInDeferredRealmUpload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- NSError *error = nil;
- if (self.isParent) {
- // Semaphore for knowing when the Realm is successfully opened for sync.
- dispatch_semaphore_t sema = dispatch_semaphore_create(0);
- RLMRealmConfiguration *config = [user configurationWithURL:url fullSynchronization:true];
- [user logOut];
- // Open a Realm after the user's been logged out.
- [self primeSyncManagerWithSemaphore:sema];
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
- XCTAssertNil(error, @"Error when opening Realm: %@", error);
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
- CHECK_COUNT(1, SyncObject, realm);
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- // Wait for the Realm's session to be bound.
- WAIT_FOR_SEMAPHORE(sema, 30);
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-2", @"parent-3"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- RLMRunChildAndWait();
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- XCTAssertNil(error, @"Error when opening Realm: %@", error);
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- }
- }
- /// A Realm that was opened while a user was logged out should be able to start downloading if the user logs back in.
- - (void)testLogBackInDeferredRealmDownload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- NSError *error = nil;
- if (self.isParent) {
- dispatch_semaphore_t sema = dispatch_semaphore_create(0);
- RLMRunChildAndWait();
- RLMRealmConfiguration *config = [user configurationWithURL:url fullSynchronization:true];
- [user logOut];
- // Open a Realm after the user's been logged out.
- [self primeSyncManagerWithSemaphore:sema];
- RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
- XCTAssertNil(error, @"Error when opening Realm: %@", error);
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
- CHECK_COUNT(1, SyncObject, realm);
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- // Wait for the Realm's session to be bound.
- WAIT_FOR_SEMAPHORE(sema, 30);
- [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@4]];
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- XCTAssertNil(error, @"Error when opening Realm: %@", error);
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(3, SyncObject, realm);
- }
- }
- /// After logging back in, a Realm whose path has been opened for the first time should properly upload changes.
- - (void)testLogBackInOpenFirstTimePathUpload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- // Now run a basic multi-client test.
- if (self.isParent) {
- // Log out the user.
- [user logOut];
- // Log the user back in.
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- // Open the Realm (for the first time).
- RLMRealm *realm = [self openRealmForURL:url user:user];
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(2, SyncObject, realm);
- RLMRunChildAndWait();
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Add objects.
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(2, SyncObject, realm);
- }
- }
- /// After logging back in, a Realm whose path has been opened for the first time should properly download changes.
- - (void)testLogBackInOpenFirstTimePathDownload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- // Now run a basic multi-client test.
- if (self.isParent) {
- // Log out the user.
- [user logOut];
- // Log the user back in.
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- // Open the Realm (for the first time).
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Run the sub-test.
- RLMRunChildAndWait();
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(2, SyncObject, realm);
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Add objects.
- [self waitForDownloadsForRealm:realm];
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(2, SyncObject, realm);
- }
- }
- /// If a client logs in, connects, logs out, and logs back in, sync should properly upload changes for a new
- /// `RLMRealm` that is opened for the same path as a previously-opened Realm.
- - (void)testLogBackInReopenRealmUpload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- // Open the Realm
- RLMRealm *realm = [self openRealmForURL:url user:user];
- if (self.isParent) {
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(1, SyncObject, realm);
- // Log out the user.
- [user logOut];
- // Log the user back in.
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- // Open the Realm again.
- realm = [self immediatelyOpenRealmForURL:url user:user];
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3", @"child-4"]];
- CHECK_COUNT(5, SyncObject, realm);
- [self waitForUploadsForRealm:realm];
- RLMRunChildAndWait();
- } else {
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(5, SyncObject, realm);
- }
- }
- /// If a client logs in, connects, logs out, and logs back in, sync should properly download changes for a new
- /// `RLMRealm` that is opened for the same path as a previously-opened Realm.
- - (void)testLogBackInReopenRealmDownload {
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- // Open the Realm
- RLMRealm *realm = [self openRealmForURL:url user:user];
- if (self.isParent) {
- [self addSyncObjectsToRealm:realm descriptions:@[@"parent-1"]];
- [self waitForUploadsForRealm:realm];
- XCTAssert([SyncObject allObjectsInRealm:realm].count == 1, @"Expected 1 item");
- // Log out the user.
- [user logOut];
- // Log the user back in.
- user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:NO]
- server:[RLMObjectServerTests authServerURL]];
- // Run the sub-test.
- RLMRunChildAndWait();
- // Open the Realm again and get the items.
- realm = [self immediatelyOpenRealmForURL:url user:user];
- [self waitForDownloadsForUser:user realms:@[realm] realmURLs:@[url] expectedCounts:@[@5]];
- } else {
- // Add objects.
- [self waitForDownloadsForRealm:realm];
- CHECK_COUNT(1, SyncObject, realm);
- [self addSyncObjectsToRealm:realm descriptions:@[@"child-1", @"child-2", @"child-3", @"child-4"]];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(5, SyncObject, realm);
- }
- }
- #pragma mark - Session suspend and resume
- - (void)testSuspendAndResume {
- NSURL *urlA = CUSTOM_REALM_URL(@"a");
- NSURL *urlB = CUSTOM_REALM_URL(@"b");
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- RLMRealm *realmA = [self openRealmForURL:urlA user:user];
- RLMRealm *realmB = [self openRealmForURL:urlB user:user];
- if (self.isParent) {
- [self waitForDownloadsForRealm:realmA];
- [self waitForDownloadsForRealm:realmB];
- CHECK_COUNT(0, SyncObject, realmA);
- CHECK_COUNT(0, SyncObject, realmB);
- // Suspend the session for realm A and then add an object to each Realm
- RLMSyncSession *sessionA = [RLMSyncSession sessionForRealm:realmA];
- [sessionA suspend];
- [self addSyncObjectsToRealm:realmA descriptions:@[@"child-A1"]];
- [self addSyncObjectsToRealm:realmB descriptions:@[@"child-B1"]];
- [self waitForUploadsForRealm:realmB];
- RLMRunChildAndWait();
- // A should still be 1 since it's suspended. If it wasn't suspended, it
- // should have downloaded before B due to the ordering in the child.
- [self waitForDownloadsForRealm:realmB];
- CHECK_COUNT(1, SyncObject, realmA);
- CHECK_COUNT(3, SyncObject, realmB);
- // A should see the other two from the child after resuming
- [sessionA resume];
- [self waitForDownloadsForRealm:realmA];
- CHECK_COUNT(3, SyncObject, realmA);
- } else {
- // Child shouldn't see the object in A
- [self waitForDownloadsForRealm:realmA];
- [self waitForDownloadsForRealm:realmB];
- CHECK_COUNT(0, SyncObject, realmA);
- CHECK_COUNT(1, SyncObject, realmB);
- [self addSyncObjectsToRealm:realmA descriptions:@[@"child-A2", @"child-A3"]];
- [self waitForUploadsForRealm:realmA];
- [self addSyncObjectsToRealm:realmB descriptions:@[@"child-B2", @"child-B3"]];
- [self waitForUploadsForRealm:realmB];
- CHECK_COUNT(2, SyncObject, realmA);
- CHECK_COUNT(3, SyncObject, realmB);
- }
- }
- #pragma mark - Client reset
- /// Ensure that a client reset error is propagated up to the binding successfully.
- - (void)testClientReset {
- NSURL *url = REALM_URL();
- NSString *sessionName = NSStringFromSelector(_cmd);
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:sessionName
- register:true]
- server:[RLMObjectServerTests authServerURL]];
- // Open the Realm
- __attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:user];
- __block NSError *theError = nil;
- XCTestExpectation *ex = [self expectationWithDescription:@"Waiting for error handler to be called..."];
- [RLMSyncManager sharedManager].errorHandler = ^void(NSError *error, RLMSyncSession *session) {
- // Make sure we're actually looking at the right session.
- XCTAssertTrue([[session.realmURL absoluteString] rangeOfString:sessionName].location != NSNotFound);
- theError = error;
- [ex fulfill];
- };
- [user simulateClientResetErrorForSession:url];
- [self waitForExpectationsWithTimeout:10 handler:nil];
- XCTAssertNotNil(theError);
- XCTAssertTrue(theError.code == RLMSyncErrorClientResetError);
- NSString *pathValue = [theError rlmSync_clientResetBackedUpRealmPath];
- XCTAssertNotNil(pathValue);
- // Sanity check the recovery path.
- NSString *recoveryPath = @"io.realm.object-server-recovered-realms/recovered_realm";
- XCTAssertTrue([pathValue rangeOfString:recoveryPath].location != NSNotFound);
- XCTAssertNotNil([theError rlmSync_errorActionToken]);
- }
- /// Test manually initiating client reset.
- - (void)testClientResetManualInitiation {
- NSURL *url = REALM_URL();
- NSString *sessionName = NSStringFromSelector(_cmd);
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:sessionName
- register:true]
- server:[RLMObjectServerTests authServerURL]];
- __block NSError *theError = nil;
- @autoreleasepool {
- __attribute__((objc_precise_lifetime)) RLMRealm *realm = [self openRealmForURL:url user:user];
- XCTestExpectation *ex = [self expectationWithDescription:@"Waiting for error handler to be called..."];
- [RLMSyncManager sharedManager].errorHandler = ^void(NSError *error, RLMSyncSession *session) {
- // Make sure we're actually looking at the right session.
- XCTAssertTrue([[session.realmURL absoluteString] rangeOfString:sessionName].location != NSNotFound);
- theError = error;
- [ex fulfill];
- };
- [user simulateClientResetErrorForSession:url];
- [self waitForExpectationsWithTimeout:10 handler:nil];
- XCTAssertNotNil(theError);
- }
- // At this point the Realm should be invalidated and client reset should be possible.
- NSString *pathValue = [theError rlmSync_clientResetBackedUpRealmPath];
- XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:pathValue]);
- [RLMSyncSession immediatelyHandleError:[theError rlmSync_errorActionToken]];
- XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:pathValue]);
- }
- #pragma mark - Progress Notifications
- - (void)testStreamingDownloadNotifier {
- const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- __block NSInteger callCount = 0;
- __block NSUInteger transferred = 0;
- __block NSUInteger transferrable = 0;
- // Open the Realm
- RLMRealm *realm = [self openRealmForURL:url user:user];
- if (self.isParent) {
- __block BOOL hasBeenFulfilled = NO;
- // Register a notifier.
- RLMSyncSession *session = [user sessionForURL:url];
- XCTAssertNotNil(session);
- XCTestExpectation *ex = [self expectationWithDescription:@"streaming-download-notifier"];
- RLMProgressNotificationToken *token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionDownload
- mode:RLMSyncProgressModeReportIndefinitely
- block:^(NSUInteger xfr, NSUInteger xfb) {
- // Make sure the values are increasing, and update our stored copies.
- XCTAssert(xfr >= transferred);
- XCTAssert(xfb >= transferrable);
- transferred = xfr;
- transferrable = xfb;
- callCount++;
- if (transferrable > 0 && transferred >= transferrable && !hasBeenFulfilled) {
- [ex fulfill];
- hasBeenFulfilled = YES;
- }
- }];
- // Wait for the child process to upload everything.
- RLMRunChildAndWait();
- [self waitForExpectationsWithTimeout:10.0 handler:nil];
- [token invalidate];
- // The notifier should have been called at least twice: once at the beginning and at least once
- // to report progress.
- XCTAssert(callCount > 1);
- XCTAssert(transferred >= transferrable,
- @"Transferred (%@) needs to be greater than or equal to transferrable (%@)",
- @(transferred), @(transferrable));
- } else {
- // Write lots of data to the Realm, then wait for it to be uploaded.
- [realm beginWriteTransaction];
- for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
- [realm addObject:[HugeSyncObject object]];
- }
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- }
- }
- - (void)testStreamingUploadNotifier {
- const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- __block NSInteger callCount = 0;
- __block NSUInteger transferred = 0;
- __block NSUInteger transferrable = 0;
- __block BOOL hasBeenFulfilled = NO;
- // Open the Realm
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Register a notifier.
- RLMSyncSession *session = [user sessionForURL:url];
- XCTAssertNotNil(session);
- XCTestExpectation *ex = [self expectationWithDescription:@"streaming-upload-expectation"];
- RLMProgressNotificationToken *token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionUpload
- mode:RLMSyncProgressModeReportIndefinitely
- block:^(NSUInteger xfr, NSUInteger xfb) {
- // Make sure the values are
- // increasing, and update our
- // stored copies.
- XCTAssert(xfr >= transferred);
- XCTAssert(xfb >= transferrable);
- transferred = xfr;
- transferrable = xfb;
- callCount++;
- if (transferred > 0
- && transferred >= transferrable
- && !hasBeenFulfilled) {
- [ex fulfill];
- hasBeenFulfilled = YES;
- }
- }];
- // Upload lots of data
- [realm beginWriteTransaction];
- for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
- [realm addObject:[HugeSyncObject object]];
- }
- [realm commitWriteTransaction];
- // Wait for upload to begin and finish
- [self waitForExpectationsWithTimeout:10.0 handler:nil];
- [token invalidate];
- // The notifier should have been called at least twice: once at the beginning and at least once
- // to report progress.
- XCTAssert(callCount > 1);
- XCTAssert(transferred >= transferrable,
- @"Transferred (%@) needs to be greater than or equal to transferrable (%@)",
- @(transferred), @(transferrable));
- }
- #pragma mark - Download Realm
- - (void)testDownloadRealm {
- const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- if (self.isParent) {
- // Wait for the child process to upload everything.
- RLMRunChildAndWait();
- XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
- RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
- XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]);
- [RLMRealm asyncOpenWithConfiguration:c
- callbackQueue:dispatch_get_main_queue()
- callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
- XCTAssertNil(error);
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- [ex fulfill];
- }];
- NSUInteger (^fileSize)(NSString *) = ^NSUInteger(NSString *path) {
- NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
- if (attributes)
- return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
- return 0;
- };
- NSUInteger sizeBefore = fileSize(c.pathOnDisk);
- @autoreleasepool {
- // We have partial transaction logs but no data
- XCTAssertGreaterThan(sizeBefore, 0U);
- XCTAssertTrue([[RLMRealm realmWithConfiguration:c error:nil] isEmpty]);
- }
- XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- [self waitForExpectationsWithTimeout:10.0 handler:nil];
- XCTAssertGreaterThan(fileSize(c.pathOnDisk), sizeBefore);
- XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Write lots of data to the Realm, then wait for it to be uploaded.
- [realm beginWriteTransaction];
- for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
- [realm addObject:[HugeSyncObject object]];
- }
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- }
- }
- - (void)testDownloadAlreadyOpenRealm {
- const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- if (!self.isParent) {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Write lots of data to the Realm, then wait for it to be uploaded.
- [realm beginWriteTransaction];
- for (NSInteger i = 0; i < NUMBER_OF_BIG_OBJECTS; i++) {
- [realm addObject:[HugeSyncObject object]];
- }
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- return;
- }
- // Wait for the child process to upload everything.
- RLMRunChildAndWait();
- XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
- RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
- XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]);
- RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil];
- CHECK_COUNT(0, HugeSyncObject, realm);
- [RLMRealm asyncOpenWithConfiguration:c
- callbackQueue:dispatch_get_main_queue()
- callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
- XCTAssertNil(error);
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- [ex fulfill];
- }];
- auto fileSize = ^NSUInteger(NSString *path) {
- NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
- return [(NSNumber *)attributes[NSFileSize] unsignedLongLongValue];
- };
- NSUInteger sizeBefore = fileSize(c.pathOnDisk);
- XCTAssertGreaterThan(sizeBefore, 0U);
- XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- XCTAssertGreaterThan(fileSize(c.pathOnDisk), sizeBefore);
- XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- }
- - (void)testDownloadWhileOpeningRealm {
- const NSInteger NUMBER_OF_BIG_OBJECTS = 2;
- NSURL *url = REALM_URL();
- // Log in the user.
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- if (self.isParent) {
- // Wait for the child process to upload everything.
- RLMRunChildAndWait();
- XCTestExpectation *ex = [self expectationWithDescription:@"download-realm"];
- RLMRealmConfiguration *c = [user configurationWithURL:url fullSynchronization:true];
- XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:c.pathOnDisk isDirectory:nil]);
- [RLMRealm asyncOpenWithConfiguration:c
- callbackQueue:dispatch_get_main_queue()
- callback:^(RLMRealm * _Nullable realm, NSError * _Nullable error) {
- XCTAssertNil(error);
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- [ex fulfill];
- }];
- RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil];
- CHECK_COUNT(0, HugeSyncObject, realm);
- XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- [self waitForExpectationsWithTimeout:10.0 handler:nil];
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- XCTAssertNotNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- } else {
- RLMRealm *realm = [self openRealmForURL:url user:user];
- // Write lots of data to the Realm, then wait for it to be uploaded.
- [realm beginWriteTransaction];
- for (NSInteger i=0; i<NUMBER_OF_BIG_OBJECTS; i++) {
- [realm addObject:[HugeSyncObject object]];
- }
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- CHECK_COUNT(NUMBER_OF_BIG_OBJECTS, HugeSyncObject, realm);
- }
- }
- - (void)testDownloadCancelsOnAuthError {
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:self.isParent]
- server:[RLMObjectServerTests authServerURL]];
- auto c = [user configurationWithURL:[NSURL URLWithString:@"realm://127.0.0.1:9080/invalid"] fullSynchronization:true];
- auto ex = [self expectationWithDescription:@"async open"];
- [RLMRealm asyncOpenWithConfiguration:c callbackQueue:dispatch_get_main_queue()
- callback:^(RLMRealm *realm, NSError *error) {
- XCTAssertNil(realm);
- XCTAssertNotNil(error);
- [ex fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- }
- #pragma mark - Compact on Launch
- - (void)testCompactOnLaunch {
- NSURL *url = REALM_URL();
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- NSString *path;
- // Create a large object and then delete it in the next transaction so that
- // the file is bloated
- @autoreleasepool {
- auto realm = [self openRealmForURL:url user:user];
- [realm beginWriteTransaction];
- [realm addObject:[HugeSyncObject object]];
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- [realm beginWriteTransaction];
- [realm deleteAllObjects];
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- [self waitForDownloadsForRealm:realm];
- path = realm.configuration.pathOnDisk;
- }
- auto fileManager = NSFileManager.defaultManager;
- auto initialSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue];
- // Reopen the file with a shouldCompactOnLaunch block and verify that it is
- // actually compacted
- auto config = [user configurationWithURL:url fullSynchronization:true];
- __block bool blockCalled = false;
- config.shouldCompactOnLaunch = ^(NSUInteger, NSUInteger){
- blockCalled = true;
- return YES;
- };
- @autoreleasepool {
- [RLMRealm realmWithConfiguration:config error:nil];
- }
- XCTAssertTrue(blockCalled);
- auto finalSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue];
- XCTAssertLessThan(finalSize, initialSize);
- XCTAssertLessThan(finalSize, 10000U);
- }
- #pragma mark - Offline Client Reset
- - (void)testOfflineClientReset {
- NSError *error;
- RLMSyncUser *user = [self logInUserForCredentials:[RLMObjectServerTests basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMObjectServerTests authServerURL]];
- NSURL *sourceFileURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"sync-1.x" withExtension:@"realm"];
- NSString *fileName = [NSString stringWithFormat:@"%@.realm", [NSUUID new]];
- NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
- [NSFileManager.defaultManager copyItemAtURL:sourceFileURL toURL:fileURL error:&error];
- XCTAssertNil(error);
- if (error) {
- return;
- }
- RLMRealmConfiguration *configuration = [user configurationWithURL:REALM_URL() fullSynchronization:true];
- RLMSyncConfiguration *syncConfig = configuration.syncConfiguration;
- syncConfig.customFileURL = fileURL;
- configuration.syncConfiguration = syncConfig;
- RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:&error];
- XCTAssertNil(realm);
- XCTAssertEqualObjects(error.domain, RLMErrorDomain);
- XCTAssertEqual(error.code, RLMErrorIncompatibleSyncedFile);
- RLMRealmConfiguration *backupConfiguration = error.userInfo[RLMBackupRealmConfigurationErrorKey];
- XCTAssertNotNil(backupConfiguration);
- // Open the backup Realm with a schema subset since it was created using the schema from .NET's unit tests.
- // The Person class is declared in SwiftObjectServerTests.swift.
- backupConfiguration.objectClasses = @[NSClassFromString(@"Person")];
- error = nil;
- RLMRealm *backupRealm = [RLMRealm realmWithConfiguration:backupConfiguration error:&error];
- XCTAssertNotNil(backupRealm);
- XCTAssertNil(error);
- RLMResults *people = [backupRealm allObjects:@"Person"];
- XCTAssertEqual(people.count, 1u);
- XCTAssertEqualObjects([people[0] valueForKey:@"FirstName"], @"John");
- XCTAssertEqualObjects([people[0] valueForKey:@"LastName"], @"Smith");
- error = nil;
- realm = [RLMRealm realmWithConfiguration:configuration error:&error];
- XCTAssertNotNil(realm);
- XCTAssertNil(error);
- }
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated"
- - (void)testAutomaticSyncConfiguration {
- NSURL *server = [RLMObjectServerTests authServerURL];
- // Automatic configuration should throw when there are no logged-in users.
- XCTAssertThrows([RLMSyncConfiguration automaticConfiguration]);
- RLMSyncCredentials *credsA = [RLMObjectServerTests basicCredentialsWithName:@"a" register:YES];
- RLMSyncUser *userA = [self logInUserForCredentials:credsA server:server];
- // Now that there's a logged-in user, we should be able to retrieve the configuration.
- RLMRealmConfiguration *configuration = [RLMSyncConfiguration automaticConfiguration];
- XCTAssert(configuration);
- @autoreleasepool {
- // And open it successfully.
- RLMRealm *realm = [self openRealmWithConfiguration:configuration];
- [self waitForDownloadsForRealm:realm];
- }
- RLMSyncCredentials *credsB = [RLMObjectServerTests basicCredentialsWithName:@"b" register:YES];
- RLMSyncUser *userB = [self logInUserForCredentials:credsB server:server];
- // Automatic configuration should throw since there's more than one logged-in user.
- XCTAssertThrows([RLMSyncConfiguration automaticConfiguration]);
- // It should still be possible to explicitly retrieve an automatic configuration for a user.
- RLMRealmConfiguration *configurationA = [RLMSyncConfiguration automaticConfigurationForUser:userA];
- XCTAssert(configurationA);
- XCTAssertEqualObjects(configuration.syncConfiguration, configurationA.syncConfiguration);
- RLMRealmConfiguration *configurationB = [RLMSyncConfiguration automaticConfigurationForUser:userB];
- XCTAssert(configurationB);
- XCTAssertNotEqualObjects(configuration.syncConfiguration, configurationB.syncConfiguration);
- [userB logOut];
- // Now that we're back to a single logged-in user, we should be able to retrieve the configuration.
- configuration = [RLMSyncConfiguration automaticConfiguration];
- XCTAssert(configuration);
- }
- #pragma clang diagnostic pop
- #pragma mark - Partial sync
- - (void)waitForKeyPath:(NSString *)keyPath object:(id)object value:(id)value {
- [self waitForExpectations:@[[[XCTKVOExpectation alloc] initWithKeyPath:keyPath object:object expectedValue:value]] timeout:20.0];
- }
- - (void)testPartialSync {
- // Make credentials.
- NSString *name = NSStringFromSelector(_cmd);
- NSURL *server = [RLMObjectServerTests authServerURL];
- // Log in and populate the Realm.
- @autoreleasepool {
- RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:YES];
- RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
- RLMRealmConfiguration *configuration = [user configuration];
- RLMRealm *realm = [self openRealmWithConfiguration:configuration];
- [realm beginWriteTransaction];
- // FIXME: make this less hideous
- // Add ten of each object
- [realm addObject:[PartialSyncObjectA objectWithNumber:0 string:@"realm"]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:1 string:@""]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:2 string:@""]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:3 string:@""]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:4 string:@"realm"]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:5 string:@"sync"]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:6 string:@"partial"]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:7 string:@"partial"]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:8 string:@"partial"]];
- [realm addObject:[PartialSyncObjectA objectWithNumber:9 string:@"partial"]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:0 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:1 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:2 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:3 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:4 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:5 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:6 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:7 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:8 firstString:@"" secondString:@""]];
- [realm addObject:[PartialSyncObjectB objectWithNumber:9 firstString:@"" secondString:@""]];
- [realm commitWriteTransaction];
- [self waitForUploadsForRealm:realm];
- }
- // Log back in and do partial sync stuff.
- @autoreleasepool {
- RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:NO];
- RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
- RLMRealmConfiguration *configuration = [user configuration];
- RLMRealm *realm = [self openRealmWithConfiguration:configuration];
- // Perform some partial sync queries
- RLMResults *objects = [PartialSyncObjectA objectsInRealm:realm where:@"number > 5"];
- RLMSyncSubscription *subscription = [objects subscribeWithName:@"query"];
- // Wait for the results to become available.
- [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
- // Verify that we got what we're looking for
- XCTAssertEqual(objects.count, 4U);
- for (PartialSyncObjectA *object in objects) {
- XCTAssertGreaterThan(object.number, 5);
- XCTAssertEqualObjects(object.string, @"partial");
- }
- // Verify that we didn't get any other objects
- XCTAssertEqual([PartialSyncObjectA allObjectsInRealm:realm].count, objects.count);
- XCTAssertEqual([PartialSyncObjectB allObjectsInRealm:realm].count, 0u);
- // Create a subscription with the same name but a different query. This should trigger an error.
- RLMResults *objects2 = [PartialSyncObjectA objectsInRealm:realm where:@"number < 5"];
- RLMSyncSubscription *subscription2 = [objects2 subscribeWithName:@"query"];
- // Wait for the error to be reported.
- [self waitForKeyPath:@"state" object:subscription2 value:@(RLMSyncSubscriptionStateError)];
- XCTAssertNotNil(subscription2.error);
- // Unsubscribe from the query, and ensure that it correctly transitions to the invalidated state.
- [subscription unsubscribe];
- [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateInvalidated)];
- }
- }
- - (RLMRealm *)partialRealmWithName:(SEL)sel {
- NSString *name = NSStringFromSelector(sel);
- NSURL *server = [RLMObjectServerTests authServerURL];
- RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:name register:YES];
- RLMSyncUser *user = [self logInUserForCredentials:creds server:server];
- RLMRealmConfiguration *configuration = [user configuration];
- return [self openRealmWithConfiguration:configuration];
- }
- - (void)testAllSubscriptionsReportsNewlyCreatedSubscription {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- XCTAssertEqual(0U, realm.subscriptions.count);
- RLMSyncSubscription *subscription = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 5"]
- subscribeWithName:@"query"];
- // Should still be 0 because the subscription is created asynchronously
- XCTAssertEqual(0U, realm.subscriptions.count);
- [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
- XCTAssertEqual(1U, realm.subscriptions.count);
- RLMSyncSubscription *subscription2 = realm.subscriptions.firstObject;
- XCTAssertEqualObjects(@"query", subscription2.name);
- XCTAssertEqual(RLMSyncSubscriptionStateComplete, subscription2.state);
- XCTAssertNil(subscription2.error);
- }
- - (void)testAllSubscriptionsDoesNotReportLocalError {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- RLMSyncSubscription *subscription1 = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 5"]
- subscribeWithName:@"query"];
- [self waitForKeyPath:@"state" object:subscription1 value:@(RLMSyncSubscriptionStateComplete)];
- RLMSyncSubscription *subscription2 = [[PartialSyncObjectA objectsInRealm:realm where:@"number > 6"]
- subscribeWithName:@"query"];
- [self waitForKeyPath:@"state" object:subscription2 value:@(RLMSyncSubscriptionStateError)];
- XCTAssertEqual(1U, realm.subscriptions.count);
- }
- - (void)testAllSubscriptionsReportsServerError {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- 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"]
- subscribeWithName:@"query"];
- XCTAssertEqual(0U, realm.subscriptions.count);
- [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateError)];
- XCTAssertEqual(1U, realm.subscriptions.count);
- RLMSyncSubscription *subscription2 = realm.subscriptions.lastObject;
- XCTAssertEqualObjects(@"query", subscription2.name);
- XCTAssertEqual(RLMSyncSubscriptionStateError, subscription2.state);
- XCTAssertNotNil(subscription2.error);
- }
- - (void)testUnsubscribeUsingOriginalSubscriptionObservingFetched {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
- [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
- XCTAssertEqual(1U, realm.subscriptions.count);
- RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
- [original unsubscribe];
- [self waitForKeyPath:@"state" object:fetched value:@(RLMSyncSubscriptionStateInvalidated)];
- XCTAssertEqual(0U, realm.subscriptions.count);
- XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, original.state);
- }
- - (void)testUnsubscribeUsingFetchedSubscriptionObservingFetched {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
- [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
- XCTAssertEqual(1U, realm.subscriptions.count);
- RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
- [fetched unsubscribe];
- [self waitForKeyPath:@"state" object:fetched value:@(RLMSyncSubscriptionStateInvalidated)];
- XCTAssertEqual(0U, realm.subscriptions.count);
- XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, original.state);
- }
- - (void)testUnsubscribeUsingFetchedSubscriptionObservingOriginal {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- RLMSyncSubscription *original = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
- [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateComplete)];
- XCTAssertEqual(1U, realm.subscriptions.count);
- RLMSyncSubscription *fetched = realm.subscriptions.firstObject;
- [fetched unsubscribe];
- [self waitForKeyPath:@"state" object:original value:@(RLMSyncSubscriptionStateInvalidated)];
- XCTAssertEqual(0U, realm.subscriptions.count);
- XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, fetched.state);
- }
- - (void)testSubscriptionWithName {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- XCTAssertNil([realm subscriptionWithName:@"query"]);
- RLMSyncSubscription *subscription = [[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query"];
- XCTAssertNil([realm subscriptionWithName:@"query"]);
- [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateComplete)];
- XCTAssertNotNil([realm subscriptionWithName:@"query"]);
- XCTAssertNil([realm subscriptionWithName:@"query2"]);
- RLMSyncSubscription *subscription2 = [realm subscriptionWithName:@"query"];
- XCTAssertEqualObjects(@"query", subscription2.name);
- XCTAssertEqual(RLMSyncSubscriptionStateComplete, subscription2.state);
- XCTAssertNil(subscription2.error);
- [subscription unsubscribe];
- XCTAssertNotNil([realm subscriptionWithName:@"query"]);
- [self waitForKeyPath:@"state" object:subscription value:@(RLMSyncSubscriptionStateInvalidated)];
- XCTAssertNil([realm subscriptionWithName:@"query"]);
- XCTAssertEqual(RLMSyncSubscriptionStateInvalidated, subscription2.state);
- }
- - (void)testSortAndFilterSubscriptions {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- [self waitForKeyPath:@"state" object:[[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query 1"]
- value:@(RLMSyncSubscriptionStateComplete)];
- [self waitForKeyPath:@"state" object:[[PartialSyncObjectA allObjectsInRealm:realm] subscribeWithName:@"query 2"]
- value:@(RLMSyncSubscriptionStateComplete)];
- [self waitForKeyPath:@"state" object:[[PartialSyncObjectB allObjectsInRealm:realm] subscribeWithName:@"query 3"]
- value:@(RLMSyncSubscriptionStateComplete)];
- 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"];
- [self waitForKeyPath:@"state" object:[unsupportedQuery subscribeWithName:@"query 4"]
- value:@(RLMSyncSubscriptionStateError)];
- auto subscriptions = realm.subscriptions;
- XCTAssertEqual(4U, subscriptions.count);
- XCTAssertEqual(0U, ([subscriptions objectsWhere:@"name = %@", @"query 0"].count));
- XCTAssertEqualObjects(@"query 1", ([subscriptions objectsWhere:@"name = %@", @"query 1"].firstObject.name));
- XCTAssertEqual(3U, ([subscriptions objectsWhere:@"status = %@", @(RLMSyncSubscriptionStateComplete)].count));
- XCTAssertEqual(1U, ([subscriptions objectsWhere:@"status = %@", @(RLMSyncSubscriptionStateError)].count));
- XCTAssertThrows([subscriptions sortedResultsUsingKeyPath:@"name" ascending:NO]);
- XCTAssertThrows([subscriptions sortedResultsUsingDescriptors:@[]]);
- XCTAssertThrows([subscriptions distinctResultsUsingKeyPaths:@[@"name"]]);
- }
- #pragma mark - Certificate pinning
- - (void)attemptLoginWithUsername:(NSString *)userName callback:(void (^)(RLMSyncUser *, NSError *))callback {
- NSURL *url = [RLMObjectServerTests secureAuthServerURL];
- RLMSyncCredentials *creds = [RLMObjectServerTests basicCredentialsWithName:userName register:YES];
- XCTestExpectation *expectation = [self expectationWithDescription:@"HTTP login"];
- [RLMSyncUser logInWithCredentials:creds authServerURL:url
- onCompletion:^(RLMSyncUser *user, NSError *error) {
- callback(user, error);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:4.0 handler:nil];
- }
- - (void)testHTTPSLoginFailsWithoutCertificate {
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
- XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
- }];
- }
- static NSURL *certificateURL(NSString *filename) {
- return [NSURL fileURLWithPath:[[[@(__FILE__) stringByDeletingLastPathComponent]
- stringByAppendingPathComponent:@"certificates"]
- stringByAppendingPathComponent:filename]];
- }
- - (void)testHTTPSLoginFailsWithIncorrectCertificate {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"not-localhost.cer")};
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
- XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
- }];
- }
- - (void)testHTTPSLoginFailsWithInvalidPathToCertificate {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"nonexistent.pem")};
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, NSCocoaErrorDomain);
- XCTAssertEqual(error.code, NSFileReadNoSuchFileError);
- }];
- }
- - (void)testHTTPSLoginFailsWithDifferentValidCert {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost-other.cer")};
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
- XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
- }];
- }
- - (void)testHTTPSLoginFailsWithFileThatIsNotACert {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"../test-ros-server.js")};
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, NSOSStatusErrorDomain);
- XCTAssertEqual(error.code, errSecUnknownFormat);
- }];
- }
- - (void)testHTTPSLoginDoesNotUseCertificateForDifferentDomain {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"example.com": certificateURL(@"localhost.cer")};
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(error.domain, NSURLErrorDomain);
- XCTAssertEqual(error.code, NSURLErrorServerCertificateUntrusted);
- }];
- }
- - (void)testHTTPSLoginSucceedsWithValidSelfSignedCertificate {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNotNil(user);
- XCTAssertNil(error);
- }];
- }
- - (void)testConfigurationFromUserAutomaticallyUsesCert {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
- __block RLMSyncUser *user;
- [self attemptLoginWithUsername:NSStringFromSelector(_cmd) callback:^(RLMSyncUser *u, NSError *error) {
- XCTAssertNotNil(u);
- XCTAssertNil(error);
- user = u;
- }];
- RLMRealmConfiguration *config = [user configuration];
- XCTAssertEqualObjects(config.syncConfiguration.realmURL.scheme, @"realms");
- XCTAssertEqualObjects(config.syncConfiguration.pinnedCertificateURL, certificateURL(@"localhost.cer"));
- // Verify that we can actually open the Realm
- auto realm = [self openRealmWithConfiguration:config];
- NSError *error;
- [self waitForUploadsForRealm:realm error:&error];
- XCTAssertNil(error);
- }
- - (void)verifyOpenSucceeds:(RLMRealmConfiguration *)config {
- auto realm = [self openRealmWithConfiguration:config];
- NSError *error;
- [self waitForUploadsForRealm:realm error:&error];
- XCTAssertNil(error);
- }
- - (void)verifyOpenFails:(RLMRealmConfiguration *)config {
- [self openRealmWithConfiguration:config];
- XCTestExpectation *expectation = [self expectationWithDescription:@"wait for error"];
- RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, __unused RLMSyncSession *session) {
- XCTAssertTrue([error.domain isEqualToString:RLMSyncErrorDomain]);
- XCTAssertFalse([[error.userInfo[kRLMSyncUnderlyingErrorKey] domain] isEqualToString:RLMSyncErrorDomain]);
- [expectation fulfill];
- };
- [self waitForExpectationsWithTimeout:20.0 handler:nil];
- }
- - (void)testConfigurationFromInsecureUserAutomaticallyUsesCert {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"localhost.cer")};
- RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMSyncTestCase authServerURL]];
- RLMRealmConfiguration *config = [user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]];
- XCTAssertEqualObjects(config.syncConfiguration.realmURL.scheme, @"realms");
- XCTAssertEqualObjects(config.syncConfiguration.pinnedCertificateURL, certificateURL(@"localhost.cer"));
- [self verifyOpenSucceeds:config];
- }
- - (void)testOpenSecureRealmWithNoCert {
- RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMSyncTestCase authServerURL]];
- [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
- }
- - (void)testOpenSecureRealmWithIncorrectCert {
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"not-localhost.cer")};
- RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMSyncTestCase authServerURL]];
- [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
- }
- - (void)DISABLE_testOpenSecureRealmWithMissingCertFile {
- // FIXME: this currently crashes inside the sync library
- RLMSyncManager.sharedManager.pinnedCertificatePaths = @{@"localhost": certificateURL(@"nonexistent.pem")};
- RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[RLMSyncTestCase authServerURL]];
- [self verifyOpenFails:[user configurationWithURL:[NSURL URLWithString:@"realms://localhost:9443/~/default"]]];
- }
- @end
|