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@
25 #import "keychain/ckks/CKKSAnalyticsLogger.h"
26 #import <Security/SFSQLite.h>
27 #import <Foundation/Foundation.h>
28 #import <XCTest/XCTest.h>
30 static NSString* tablePath = nil;
32 @interface SQLiteTests : XCTestCase
35 @implementation SQLiteTests
41 tablePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test_table.db"];
48 [[NSFileManager defaultManager] removeItemAtPath:tablePath error:nil];
53 [[NSFileManager defaultManager] removeItemAtPath:tablePath error:nil];
58 - (void)testCreateLoggingDatabase
60 NSString* schema = @"CREATE table test (test_column INTEGER);";
61 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
63 XCTAssertTrue([sqlTable openWithError:&error], @"failed to open sql database");
64 XCTAssertNil(error, "encountered error opening database: %@", error);
65 XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:tablePath]);
69 - (void)testInsertAndDelete
71 NSString* schema = @"CREATE table test (test_column INTEGER);";
72 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
74 XCTAssertTrue([sqlTable openWithError:&error], @"failed to open sql database");
75 XCTAssertNil(error, "encountered error opening database: %@", error);
77 [sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(1)}];
78 [sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(2)}];
79 [sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(3)}];
80 XCTAssertTrue([[sqlTable selectAllFrom:@"test" where:nil bindings:nil] count] == 3);
82 [sqlTable deleteFrom:@"test" where:@"test_column = ?" bindings:@[@3]];
83 XCTAssertTrue([[sqlTable selectAllFrom:@"test" where:nil bindings:nil] count] == 2);
85 [sqlTable executeSQL:@"delete from test"];
86 XCTAssertTrue([[sqlTable selectAllFrom:@"test" where:nil bindings:nil] count] == 0);
89 - (void)testDontCrashWhenThereAreNoWritePermissions
91 NSString* schema = @"CREATE table test (test_column INTEGER);";
92 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
95 XCTAssertNoThrow([sqlTable openWithError:&error], @"opening database threw an exception");
96 XCTAssertNil(error, "encountered error opening database: %@", error);
97 XCTAssertNoThrow([sqlTable close], @"closing database threw an exception");
99 NSDictionary* originalAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:tablePath error:&error];
100 XCTAssertNil(error, @"encountered error getting database file attributes: %@", error);
102 [[NSFileManager defaultManager] setAttributes:@{NSFilePosixPermissions : @(400), NSFileImmutable : @(YES)} ofItemAtPath:tablePath error:&error];
103 XCTAssertNil(error, @"encountered error setting database file attributes: %@", error);
104 XCTAssertNoThrow([sqlTable openWithError:&error]);
105 XCTAssertNotNil(error, @"failed to generate error when opening file without permissions");
108 [[NSFileManager defaultManager] setAttributes:originalAttributes ofItemAtPath:tablePath error:&error];
109 XCTAssertNil(error, @"encountered error setting database file attributes back to original attributes: %@", error);
112 - (void)testDontCrashFromInternalErrors
114 NSString* schema = @"CREATE table test (test_column INTEGER);";
115 SFSQLite* sqlTable = [[SFSQLite alloc] initWithPath:tablePath schema:schema];
117 NSError* error = nil;
118 XCTAssertTrue([sqlTable openWithError:&error], @"failed to open database");
119 XCTAssertNil(error, "encountered error opening database: %@", error);
121 // delete the database to create havoc
122 [[NSFileManager defaultManager] removeItemAtPath:tablePath error:nil];
124 XCTAssertNoThrow([sqlTable insertOrReplaceInto:@"test" values:@{@"test_column" : @(1)}], @"inserting into deleted table threw an exception");
129 @interface CKKSAnalyticsLoggerTests : CloudKitKeychainSyncingTestsBase
132 @implementation CKKSAnalyticsLoggerTests
134 - (void)testLoggingJSONGenerated
136 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
138 // We expect a single record to be uploaded.
139 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
141 [self startCKKSSubsystem];
143 [self addGenericPassword: @"data" account: @"account-delete-me"];
145 OCMVerifyAllWithDelay(self.mockDatabase, 8);
147 NSError* error = nil;
148 NSData* json = [[CKKSAnalyticsLogger logger] getLoggingJSON:false error:&error];
149 XCTAssertNotNil(json, @"failed to generate logging json");
150 XCTAssertNil(error, @"encourntered error getting logging json: %@", error);
152 NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
153 XCTAssertNotNil(dictionary, @"failed to generate dictionary from json data");
154 XCTAssertNil(error, @"encountered error deserializing json: %@", error);
155 XCTAssertTrue([dictionary isKindOfClass:[NSDictionary class]], @"did not get the class we expected from json deserialization");
157 XCTAssertNotNil(dictionary[@"postTime"], @"Failed to get posttime");
159 NSArray *events = dictionary[@"events"];
160 XCTAssertNotNil(events, @"Failed to get events");
161 XCTAssert([events isKindOfClass:[NSArray class]], @"did not get the class we expected for events");
164 for (NSDictionary *event in events) {
165 XCTAssert([event isKindOfClass:[NSDictionary class]], @"did not get the class we expected for events");
166 XCTAssertNotNil(event[@"build"], @"Failed to get build in event");
167 XCTAssertNotNil(event[@"product"], @"Failed to get product in event");
168 XCTAssertNotNil(event[@"topic"], @"Failed to get topic in event");
170 NSString *eventtype = event[@"eventType"];
171 XCTAssertNotNil(eventtype, @"Failed to get eventType in eventtype");
172 XCTAssert([eventtype isKindOfClass:[NSString class]], @"did not get the class we expected for events");
173 if ([eventtype isEqualToString:@"ckksHealthSummary"]) {
174 XCTAssertNotNil(event[@"ckdeviceID"], @"Failed to get deviceID in event");
179 - (void)testSplunkDefaultTopicNameExists
181 CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
182 dispatch_sync(logger.splunkLoggingQueue, ^{
183 XCTAssertNotNil(logger.splunkTopicName);
187 - (void)testSplunkDefaultBagURLExists
189 CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
190 dispatch_sync(logger.splunkLoggingQueue, ^{
191 XCTAssertNotNil(logger.splunkBagURL);
195 // <rdar://problem/32983193> test_KeychainCKKS | CKKSTests failed: "Failed subtests: -[CloudKitKeychainSyncingTests testSplunkUploadURLExists]" [j71ap][CoreOSTigris15Z240][bats-e-27-204-1]
197 - (void)testSplunkUploadURLExists
199 CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
200 dispatch_sync(logger.splunkLoggingQueue, ^{
201 logger.ignoreServerDisablingMessages = YES;
202 XCTAssertNotNil(logger.splunkUploadURL);
207 - (void)testLastSuccessfulSyncDate
209 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
210 [self startCKKSSubsystem];
211 CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
212 [self.keychainZone addToZone: ckr];
214 // Trigger a notification (with hilariously fake data)
215 [self.keychainView notifyZoneChange:nil];
217 [[[self.keychainView waitForFetchAndIncomingQueueProcessing] completionHandlerDidRunCondition] wait:4 * NSEC_PER_SEC];
219 NSDate* syncDate = [[CKKSAnalyticsLogger logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassC inView:self.keychainView];
220 XCTAssertNotNil(syncDate, "Failed to get a last successful sync date");
221 NSDate* nowDate = [NSDate dateWithTimeIntervalSinceNow:0];
222 NSTimeInterval timeIntervalSinceSyncDate = [nowDate timeIntervalSinceDate:syncDate];
223 XCTAssertTrue(timeIntervalSinceSyncDate >= 0.0 && timeIntervalSinceSyncDate <= 15.0, "Last sync date does not look like a reasonable one");
226 - (void)testExtraValuesToUploadToServer
228 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
229 [self startCKKSSubsystem];
230 CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
231 [self.keychainZone addToZone: ckr];
233 // Trigger a notification (with hilariously fake data)
234 [self.keychainView notifyZoneChange:nil];
236 [[[self.keychainView waitForFetchAndIncomingQueueProcessing] completionHandlerDidRunCondition] wait:4 * NSEC_PER_SEC];
238 NSDictionary* extraValues = [[CKKSAnalyticsLogger logger] extraValuesToUploadToServer];
239 XCTAssertTrue([extraValues[@"inCircle"] boolValue]);
240 XCTAssertTrue([extraValues[@"keychain-TLKs"] boolValue]);
241 XCTAssertTrue([extraValues[@"keychain-inSyncA"] boolValue]);
242 XCTAssertTrue([extraValues[@"keychain-inSyncC"] boolValue]);
243 XCTAssertTrue([extraValues[@"keychain-IQNOE"] boolValue]);
244 XCTAssertTrue([extraValues[@"keychain-OQNOE"] boolValue]);
245 XCTAssertTrue([extraValues[@"keychain-inSync"] boolValue]);
248 - (void)testNilEventDoesNotCrashTheSystem
250 CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
251 [logger logSuccessForEventNamed:nil];
254 NSError* error = nil;
255 XCTAssertNoThrow(json = [logger getLoggingJSON:false error:&error]);
256 XCTAssertNotNil(json, @"Failed to get JSON after logging nil event");
257 XCTAssertNil(error, @"Got error when grabbing JSON after logging nil event: %@", error);
260 - (void)testRaceToCreateLoggers
262 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
263 for (NSInteger i = 0; i < 5; i++) {
264 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
265 CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
266 [logger logSuccessForEvent:(CKKSAnalyticsFailableEvent*)@"test_event" inView:self.keychainView];
267 dispatch_semaphore_signal(semaphore);
271 for (NSInteger i = 0; i < 5; i++) {
272 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
276 - (void)testSysdiagnoseDump
278 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
279 [self startCKKSSubsystem];
280 CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
281 [self.keychainZone addToZone: ckr];
283 // Trigger a notification (with hilariously fake data)
284 [self.keychainView notifyZoneChange:nil];
286 [self.keychainView waitForFetchAndIncomingQueueProcessing];
288 NSError* error = nil;
289 NSString* sysdiagnose = [[CKKSAnalyticsLogger logger] getSysdiagnoseDumpWithError:&error];
290 XCTAssertNil(error, @"encountered an error grabbing CKKS analytics sysdiagnose: %@", error);
291 XCTAssertTrue(sysdiagnose.length > 0, @"failed to get a sysdiagnose from CKKS analytics");