2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 #import <Foundation/Foundation.h>
28 #import <XCTest/XCTest.h>
29 #import <OCMock/OCMock.h>
31 #import "OTTestsBase.h"
33 static NSString* OTCKRecordBottledPeerType = @"OTBottledPeer";
34 static NSString* OTCKRecordEscrowRecordID = @"escrowRecordID";
36 @interface OTCloudStoreUnitTests : OTTestsBase
37 @property (nonatomic, strong) OTBottledPeerRecord* fakeBottledPeerRecord;
40 @implementation OTCloudStoreUnitTests
45 self.continueAfterFailure = NO;
46 self.fakeBottledPeerRecord = [[OTBottledPeerRecord alloc] init];
47 self.fakeBottledPeerRecord.bottle = [@"bottled peer data" dataUsingEncoding:NSUTF8StringEncoding];
48 self.fakeBottledPeerRecord.signatureUsingEscrowKey = [@"bottled peer escrow sig" dataUsingEncoding:NSUTF8StringEncoding];
49 self.fakeBottledPeerRecord.signatureUsingPeerKey = [@"bottled peer peer sig" dataUsingEncoding:NSUTF8StringEncoding];
50 self.fakeBottledPeerRecord.peerID = @"peer id";
51 self.fakeBottledPeerRecord.spID = @"sos peer id";
52 self.fakeBottledPeerRecord.escrowRecordID = @"escrowRecordID";
53 self.fakeBottledPeerRecord.escrowedSigningSPKI = [@"escrowedSigningSPKI" dataUsingEncoding:kCFStringEncodingUTF8];
54 self.fakeBottledPeerRecord.peerSigningSPKI = [@"peerSigningSPKI" dataUsingEncoding:kCFStringEncodingUTF8];
59 self.operationQueue = nil;
64 - (void)testWriteSameBottledPeerTwiceToFakeRecord {
67 NSMutableDictionary* recordDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:[[NSNumber alloc] initWithInt:1], OTCKRecordBottledPeerType, nil];
69 [self expectAddedCKModifyRecords:recordDictionary holdFetch:YES];
70 [self startCKKSSubsystem];
71 XCTAssertTrue([self.cloudStore uploadBottledPeerRecord:self.fakeBottledPeerRecord escrowRecordID:self.fakeBottledPeerRecord.escrowRecordID error:&error], @"should create bottled peer record");
72 XCTAssertNil(error, "error should be nil");
74 [self waitForCKModifications];
75 OCMVerifyAllWithDelay(self.mockDatabase, 8);
76 [self releaseCloudKitFetchHold];
78 [self expectAddedCKModifyRecords:recordDictionary holdFetch:YES];
80 XCTAssertTrue([self.cloudStore uploadBottledPeerRecord:self.fakeBottledPeerRecord escrowRecordID:self.fakeBottledPeerRecord.escrowRecordID error:&error], @"should create bottled peer record");
81 XCTAssertNil(error, "error should be nil");
83 [self waitForCKModifications];
84 OCMVerifyAllWithDelay(self.mockDatabase, 8);
85 [self releaseCloudKitFetchHold];
88 - (void)testWriteBottledPeerToFakeRecord {
91 NSMutableDictionary* recordDictionary = [NSMutableDictionary dictionary];
92 recordDictionary[OTCKRecordBottledPeerType] = [[NSNumber alloc] initWithInt:1];
94 [self expectAddedCKModifyRecords:recordDictionary holdFetch:YES];
95 [self startCKKSSubsystem];
97 XCTAssertTrue([self.cloudStore uploadBottledPeerRecord:self.fakeBottledPeerRecord escrowRecordID:self.fakeBottledPeerRecord.escrowRecordID error:&error], @"should create bottled peer record");
98 XCTAssertNil(error, "error should be nil");
100 [self waitForCKModifications];
101 OCMVerifyAllWithDelay(self.mockDatabase, 8);
102 [self releaseCloudKitFetchHold];
105 - (void)testWriteMultipleBottledPeersToSAMEFakeRecord {
106 NSError* error = nil;
108 NSMutableDictionary* recordDictionary = [NSMutableDictionary dictionary];
110 recordDictionary[OTCKRecordBottledPeerType] = [[NSNumber alloc] initWithInt:1];
112 [self startCKKSSubsystem];
114 for(int i = 0; i < 10; i++){
115 [self expectAddedCKModifyRecords:recordDictionary holdFetch:NO];
117 XCTAssertTrue([self.cloudStore uploadBottledPeerRecord:self.fakeBottledPeerRecord escrowRecordID:self.fakeBottledPeerRecord.escrowRecordID error:&error], @"should create bottled peer record");
119 [self waitForCKModifications];
121 XCTAssertNil(error, "error should be nil");
122 OCMVerifyAllWithDelay(self.mockDatabase, 8);
123 [self releaseCloudKitFetchHold];
127 - (void)testWriteBottledPeersToDifferentFakeRecord {
128 NSError* error = nil;
130 NSMutableDictionary* recordDictionary = [NSMutableDictionary dictionary];
132 recordDictionary[OTCKRecordBottledPeerType] = [[NSNumber alloc] initWithInt:1];
134 [self startCKKSSubsystem];
136 for(int i = 0; i < 10; i++){
137 [self expectAddedCKModifyRecords:recordDictionary holdFetch:YES];
138 NSString *escrowID = [NSString stringWithFormat:@"bp-sospeer%d-hash", i];
139 self.fakeBottledPeerRecord.escrowRecordID = escrowID;
140 XCTAssertTrue([self.cloudStore uploadBottledPeerRecord:self.fakeBottledPeerRecord escrowRecordID:escrowID error:&error], @"should create bottled peer record");
141 [self waitForCKModifications];
143 XCTAssertNil(error, "error should be nil");
144 OCMVerifyAllWithDelay(self.mockDatabase, 8);
145 [self releaseCloudKitFetchHold];
147 XCTAssertTrue( [[self.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error] count] == 10, @"should have 1 record");
151 - (void)testReadBottledPeerRecordFromCloudKit {
152 NSError *error = nil;
153 [self startCKKSSubsystem];
155 CKRecord* newRecord = [[CKRecord alloc]initWithRecordType:OTCKRecordBottledPeerType];
156 newRecord[OTCKRecordEscrowRecordID] = @"escrowRecordID";
157 [self.otFakeZone addToZone:newRecord];
159 [self.cloudStore notifyZoneChange:nil];
161 [self waitForCKModifications];
163 OCMVerifyAllWithDelay(self.mockDatabase, 8);
164 XCTAssertTrue( [[self.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error] count] > 0, @"should have 1 record");
167 -(void) testOTCloudStoreDownloadBP{
168 NSError* error = nil;
169 [self startCKKSSubsystem];
171 CKRecord* newRecord = [[CKRecord alloc]initWithRecordType:OTCKRecordBottledPeerType];
172 newRecord[OTCKRecordEscrowRecordID] = @"escrowRecordID";
173 [self.otFakeZone addToZone:newRecord];
175 XCTAssertTrue([self.cloudStore downloadBottledPeerRecord:&error] == YES, @"downloading records should succeed:%@", error);
176 XCTAssertNil(error, @"error should be nil");
178 [self waitForCKModifications];
180 OCMVerifyAllWithDelay(self.mockDatabase, 8);
182 XCTAssertNil(error, "error should be nil");
183 XCTAssertEqual([[self.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error] count], (unsigned long)1, @"should have 1 record");
184 XCTAssertNil(error, "error should be nil");
187 -(void) testOTCloudStoreDownloadMultipleBP{
188 NSError* error = nil;
189 [self startCKKSSubsystem];
191 for(int i = 0; i < 10; i++){
192 CKRecord* newRecord = [[CKRecord alloc]initWithRecordType:OTCKRecordBottledPeerType zoneID:self.otZoneID];
193 newRecord[OTCKRecordEscrowRecordID] = [NSString stringWithFormat:@"escrowRecordID%d", i];
194 [self.otFakeZone addToZone:newRecord];
196 [self waitForCKModifications];
198 XCTAssertTrue([self.cloudStore downloadBottledPeerRecord:&error] == YES, @"downloading records should succeed:%@", error);
199 XCTAssertNil(error, @"error should be nil");
200 [self waitForCKModifications];
202 OCMVerifyAllWithDelay(self.mockDatabase, 8);
204 XCTAssertNil(error, "error should be nil");
205 XCTAssertEqual( [[self.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error] count], (unsigned long)10, @"should have 1 record");
208 -(void) testOTCloudStoreUploadMultipleToSameRecord{
209 NSError* error = nil;
210 [self startCKKSSubsystem];
211 CKRecord* newRecord = [[CKRecord alloc]initWithRecordType:OTCKRecordBottledPeerType zoneID:self.otZoneID];
212 newRecord[OTCKRecordEscrowRecordID] = @"escrowRecordID";
213 for(int i = 0; i < 10; i++){
214 [self.otFakeZone addToZone:newRecord];
216 [self waitForCKModifications];
218 XCTAssertTrue([self.cloudStore downloadBottledPeerRecord:&error] == YES, @"downloading records should succeed:%@", error);
219 XCTAssertNil(error, @"error should be nil");
220 [self waitForCKModifications];
222 OCMVerifyAllWithDelay(self.mockDatabase, 8);
224 XCTAssertNil(error, "error should be nil");
225 XCTAssertEqual([[self.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error] count], (unsigned long)1, @"should have 1 record");
228 -(void) testRemoveRecordIDs{
230 [self startCKKSSubsystem];
231 NSError *error = nil;
232 CKRecord* newRecord = [[CKRecord alloc]initWithRecordType:OTCKRecordBottledPeerType zoneID:self.otZoneID];
233 newRecord[OTCKRecordEscrowRecordID] = @"escrowRecordID";
234 [self expectCKFetch];
236 [self.otFakeZone addToZone:newRecord];
237 [self waitForCKModifications];
239 [self.cloudStore notifyZoneChange:nil];
240 [self waitForCKModifications];
242 XCTAssertTrue( [[self.cloudStore retrieveListOfEligibleEscrowRecordIDs:&error] count] == 1, @"should have 1 record");
244 [self expectCKFetch];
245 XCTAssertTrue([self.cloudStore downloadBottledPeerRecord:&error] == YES, @"downloading records should succeed:%@", error);
246 XCTAssertNil(error, @"error should be nil");
247 [self waitForCKModifications];
250 -(void) testFetchTimeout
252 [self startCKKSSubsystem];
254 NSError* error = nil;
255 CKRecord* newRecord = [[CKRecord alloc]initWithRecordType:OTCKRecordBottledPeerType zoneID:self.otZoneID];
256 newRecord[OTCKRecordEscrowRecordID] = @"escrowRecordID";
258 [self holdCloudKitFetches];
260 [self.cloudStore downloadBottledPeerRecord:&error];
262 XCTAssertNotNil(error, "error should not be nil");
263 XCTAssertTrue([(NSString*)error.userInfo[@"NSLocalizedDescription"] isEqualToString:@"Operation(CKKSResultOperation(cloudkit-fetch-and-process-changes)) timed out waiting to start for [<CKKSResultOperation(fetch-and-process-updates-watcher): ready>]"], "expecting timed out error");
266 -(void) testModifyRecordsTimeout
268 NSError* error = nil;
270 NSMutableDictionary* recordDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:[[NSNumber alloc] initWithInt:1], OTCKRecordBottledPeerType, nil];
272 [self expectAddedCKModifyRecords:recordDictionary holdFetch:NO];
274 [self startCKKSSubsystem];
276 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
277 [self startCKKSSubsystem];
279 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:4*NSEC_PER_SEC], @"Key state should have arrived at ready");
281 [self holdCloudKitModifications];
283 [self.cloudStore uploadBottledPeerRecord:self.fakeBottledPeerRecord
284 escrowRecordID:self.fakeBottledPeerRecord.escrowRecordID error:&error];
286 XCTAssertNotNil(error, "error should not be nil");
287 XCTAssertTrue([(NSString*)error.userInfo[@"NSLocalizedDescription"] isEqualToString:@"Operation(CKKSResultOperation(cloudkit-modify-changes)) timed out waiting to start for [<CKKSResultOperation(modify-records-watcher): ready>]"], "expecting timed out error");
289 [self expectAddedCKModifyRecords:recordDictionary holdFetch:NO];
291 [self releaseCloudKitModificationHold];
292 [self waitForCKModifications];