2 * Copyright (c) 2016 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 "keychain/ckks/CKKS.h"
27 #import "keychain/ckks/tests/CKKSTests.h"
28 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
31 @interface CKKSKeychainView(test)
32 @property NSOperationQueue* operationQueue;
35 @implementation CloudKitKeychainSyncingTests (CoalesceTests)
36 // These tests check that, if CKKS doesn't start processing an item before a new update comes in,
37 // each case is properly handled.
39 - (void)testCoalesceAddModifyItem {
40 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
42 NSString* account = @"account-delete-me";
44 [self addGenericPassword: @"data" account: account];
45 [self updateGenericPassword: @"otherdata" account:account];
47 // We expect a single record to be uploaded.
48 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
50 [self startCKKSSubsystem];
51 OCMVerifyAllWithDelay(self.mockDatabase, 20);
54 - (void)testCoalesceAddModifyModifyItem {
55 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
57 NSString* account = @"account-delete-me";
59 [self addGenericPassword: @"data" account: account];
60 [self updateGenericPassword: @"otherdata" account:account];
61 [self updateGenericPassword: @"again" account:account];
63 // We expect a single record to be uploaded.
64 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
66 [self startCKKSSubsystem];
67 OCMVerifyAllWithDelay(self.mockDatabase, 20);
70 - (void)testCoalesceAddModifyDeleteItem {
71 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
73 NSString* account = @"account-delete-me";
75 [self addGenericPassword: @"data" account: account];
76 [self updateGenericPassword: @"otherdata" account:account];
77 [self deleteGenericPassword: account];
79 // We expect no uploads.
80 [self startCKKSSubsystem];
81 [self.keychainView waitUntilAllOperationsAreFinished];
82 OCMVerifyAllWithDelay(self.mockDatabase, 20);
85 - (void)testCoalesceDeleteAddItem {
86 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
88 NSString* account = @"account-delete-me";
90 [self addGenericPassword: @"data" account: account];
92 // We expect a single record to be uploaded.
93 [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
94 [self startCKKSSubsystem];
95 OCMVerifyAllWithDelay(self.mockDatabase, 20);
96 [self waitForCKModifications];
98 // Okay, now the delete/add. Note that this is not a coalescing operation, since the new item
99 // will have a completely different UUID, and so will delete the old record and upload the new one.
101 [self.keychainView.operationQueue waitUntilAllOperationsAreFinished];
102 self.keychainView.operationQueue.suspended = YES;
103 [self deleteGenericPassword: account];
104 [self addGenericPassword: @"data" account: account];
106 [self expectCKModifyRecords:@{SecCKRecordItemType: [NSNumber numberWithUnsignedInteger: 1],
107 SecCKRecordCurrentKeyType: [NSNumber numberWithUnsignedInteger: 1],
108 SecCKRecordDeviceStateType: [NSNumber numberWithUnsignedInteger: 1],
110 deletedRecordTypeCounts:@{SecCKRecordItemType: [NSNumber numberWithUnsignedInteger: 1]}
111 zoneID:self.keychainZoneID
112 checkModifiedRecord:nil
113 runAfterModification:nil];
114 self.keychainView.operationQueue.suspended = NO;
115 OCMVerifyAllWithDelay(self.mockDatabase, 20);