1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257 |
- ////////////////////////////////////////////////////////////////////////////
- //
- // 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 "RLMRealm+Sync.h"
- #import "RLMRealmConfiguration_Private.h"
- #import "RLMRealmUtil.hpp"
- #import "RLMRealm_Dynamic.h"
- #import "RLMRealm_Private.hpp"
- #import "RLMSyncUtil_Private.h"
- #import "shared_realm.hpp"
- #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
- @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;
- // 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"];
- auto 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 && transferrable > 1000000 * NUMBER_OF_BIG_OBJECTS) {
- [ex fulfill];
- }
- }];
- // 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;
- };
- XCTAssertNil(RLMGetAnyCachedRealmForPath(c.pathOnDisk.UTF8String));
- [self waitForExpectationsWithTimeout:10.0 handler:nil];
- XCTAssertGreaterThan(fileSize(c.pathOnDisk), 0U);
- 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;
- }
- 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);
- [realm.syncSession suspend];
- // Wait for the child process to upload everything.
- RLMRunChildAndWait();
- 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));
- [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];
- }];
- [realm.syncSession resume];
- [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)[realm configuration];
- }
- - (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;
- __block NSUInteger usedSize = 0;
- config.shouldCompactOnLaunch = ^(NSUInteger, NSUInteger used) {
- usedSize = used;
- blockCalled = true;
- return YES;
- };
- @autoreleasepool {
- [RLMRealm realmWithConfiguration:config error:nil];
- }
- XCTAssertTrue(blockCalled);
- auto finalSize = [[fileManager attributesOfItemAtPath:path error:nil][NSFileSize] unsignedLongLongValue];
- XCTAssertLessThan(finalSize, initialSize);
- XCTAssertLessThanOrEqual(finalSize, usedSize + 4096U);
- }
- #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);
- // XCTKVOExpecatation retains the object and releases it sometime later on
- // a background thread, which causes issues if the realm is closed after
- // we reset the global state
- realm->_realm->close();
- }
- - (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);
- // XCTKVOExpecatation retains the object and releases it sometime later on
- // a background thread, which causes issues if the realm is closed after
- // we reset the global state
- realm->_realm->close();
- }
- - (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];
- NSDate *now = NSDate.date;
- [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));
- XCTAssertEqual(4U, ([subscriptions objectsWhere:@"createdAt >= %@", now]).count);
- XCTAssertEqual(0U, ([subscriptions objectsWhere:@"createdAt < %@", now]).count);
- XCTAssertEqual(4U, [subscriptions objectsWhere:@"expiresAt = nil"].count);
- XCTAssertEqual(4U, [subscriptions objectsWhere:@"timeToLive = nil"].count);
- XCTAssertThrows([subscriptions sortedResultsUsingKeyPath:@"name" ascending:NO]);
- XCTAssertThrows([subscriptions sortedResultsUsingDescriptors:@[]]);
- XCTAssertThrows([subscriptions distinctResultsUsingKeyPaths:@[@"name"]]);
- }
- - (void)testIncludeLinkingObjectsErrorHandling {
- RLMRealm *realm = [self partialRealmWithName:_cmd];
- RLMResults *objects = [PersonObject allObjectsInRealm:realm];
- RLMSyncSubscriptionOptions *opt = [RLMSyncSubscriptionOptions new];
- opt.includeLinkingObjectProperties = @[@"nonexistent"];
- RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
- @"Invalid LinkingObjects inclusion from key path 'nonexistent': property 'PersonObject.nonexistent' does not exist.");
- opt.includeLinkingObjectProperties = @[@"name"];
- RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
- @"Invalid LinkingObjects inclusion from key path 'name': property 'PersonObject.name' is of unsupported type 'string'.");
- opt.includeLinkingObjectProperties = @[@"children.name"];
- RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
- @"Invalid LinkingObjects inclusion from key path 'children.name': property 'PersonObject.name' is of unsupported type 'string'.");
- opt.includeLinkingObjectProperties = @[@"children"];
- RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
- @"Invalid LinkingObjects inclusion from key path 'children': key path must end in a LinkingObjects property and 'PersonObject.children' is of type 'array'.");
- opt.includeLinkingObjectProperties = @[@"children."];
- RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
- @"Invalid LinkingObjects inclusion from key path 'children.': missing property name.");
- opt.includeLinkingObjectProperties = @[@""];
- RLMAssertThrowsWithReason([objects subscribeWithOptions:opt],
- @"Invalid LinkingObjects inclusion from key path '': missing property 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"]]];
- }
- #pragma mark - Custom request headers
- - (void)testLoginFailsWithoutCustomHeader {
- XCTestExpectation *expectation = [self expectationWithDescription:@"register user"];
- [RLMSyncUser logInWithCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- authServerURL:[NSURL URLWithString:@"http://127.0.0.1:9081"]
- onCompletion:^(RLMSyncUser *user, NSError *error) {
- XCTAssertNil(user);
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(@400, error.userInfo[@"statusCode"]);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:4.0 handler:nil];
- }
- - (void)testLoginUsesCustomHeader {
- RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
- RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[NSURL URLWithString:@"http://127.0.0.1:9081"]];
- XCTAssertNotNil(user);
- }
- - (void)testModifyCustomHeadersAfterOpeningRealm {
- RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
- RLMSyncUser *user = [self logInUserForCredentials:[RLMSyncTestCase basicCredentialsWithName:NSStringFromSelector(_cmd)
- register:YES]
- server:[NSURL URLWithString:@"http://127.0.0.1:9081"]];
- XCTAssertNotNil(user);
- RLMSyncManager.sharedManager.customRequestHeaders = nil;
- NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"realm://127.0.0.1:9081/~/%@", NSStringFromSelector(_cmd)]];
- auto c = [user configurationWithURL:url fullSynchronization:true];
- // Should initially fail to connect due to the missing header
- XCTestExpectation *ex1 = [self expectationWithDescription:@"connection failure"];
- RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, RLMSyncSession *) {
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(@400, [error.userInfo[@"underlying_error"] userInfo][@"statusCode"]);
- [ex1 fulfill];
- };
- RLMRealm *realm = [RLMRealm realmWithConfiguration:c error:nil];
- RLMSyncSession *syncSession = realm.syncSession;
- [self waitForExpectationsWithTimeout:4.0 handler:nil];
- XCTAssertEqual(syncSession.connectionState, RLMSyncConnectionStateDisconnected);
- // Should successfully connect once the header is set
- RLMSyncManager.sharedManager.errorHandler = nil;
- auto ex2 = [[XCTKVOExpectation alloc] initWithKeyPath:@"connectionState"
- object:syncSession
- expectedValue:@(RLMSyncConnectionStateConnected)];
- RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Allow-Connection": @"true"};
- [self waitForExpectations:@[ex2] timeout:4.0];
- // Should disconnect and fail to reconnect when the wrong header is set
- XCTestExpectation *ex3 = [self expectationWithDescription:@"reconnection failure"];
- RLMSyncManager.sharedManager.errorHandler = ^(NSError *error, RLMSyncSession *) {
- XCTAssertNotNil(error);
- XCTAssertEqualObjects(@400, [error.userInfo[@"underlying_error"] userInfo][@"statusCode"]);
- [ex3 fulfill];
- };
- auto ex4 = [[XCTKVOExpectation alloc] initWithKeyPath:@"connectionState"
- object:syncSession
- expectedValue:@(RLMSyncConnectionStateDisconnected)];
- RLMSyncManager.sharedManager.customRequestHeaders = @{@"X-Other-Header": @"true"};
- [self waitForExpectations:@[ex3, ex4] timeout:4.0];
- }
- @end
|