diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index c38e152..b106acf 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -24,18 +24,6 @@ runs: run: xcodebuild -version | tee .xcode-version shell: bash - - id: restore-carthage-cache - name: Restore Carthage cache - uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 - with: - path: Carthage - key: carthage-${{ inputs.platform }}-${{ hashFiles('Cartfile.resolved') }}-${{ hashFiles('.xcode-version') }}-v1 - - - name: Install dependencies - if: steps.restore-carthage-cache.outputs.cache-hit != 'true' - run: carthage bootstrap --platform ${{ inputs.platform }} --use-xcframeworks --no-use-binaries --cache-builds - shell: bash - - name: Run tests uses: mxcl/xcodebuild@6e60022a0cbe8c89278be2dd1773a2f68e7c5c87 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8a2f2ee..2453b69 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,5 @@ ## Environment setup -We use [Carthage](https://github.com/Carthage/Carthage) to manage SimpleKeychain's dependencies. - 1. Clone this repository and enter its root directory. -2. Run `carthage bootstrap --use-xcframeworks` to fetch and build the dependencies. -3. Open `SimpleKeychain.xcodeproj` in Xcode. +2. Open `SimpleKeychain.xcodeproj` in Xcode. diff --git a/Cartfile.private b/Cartfile.private deleted file mode 100644 index 2715491..0000000 --- a/Cartfile.private +++ /dev/null @@ -1,2 +0,0 @@ -github "Quick/Quick" ~> 7.0 -github "Quick/Nimble" ~> 12.0 diff --git a/Cartfile.resolved b/Cartfile.resolved deleted file mode 100644 index 87c0c61..0000000 --- a/Cartfile.resolved +++ /dev/null @@ -1,2 +0,0 @@ -github "Quick/Nimble" "v12.0.1" -github "Quick/Quick" "v7.0.2" diff --git a/Package.swift b/Package.swift index d383dc0..064f2fa 100644 --- a/Package.swift +++ b/Package.swift @@ -1,15 +1,10 @@ // swift-tools-version:5.7 - import PackageDescription let package = Package( name: "SimpleKeychain", platforms: [.iOS(.v13), .macOS(.v11), .tvOS(.v13), .watchOS(.v7)], products: [.library(name: "SimpleKeychain", targets: ["SimpleKeychain"])], - dependencies: [ - .package(url: "https://github.com/Quick/Quick.git", .upToNextMajor(from: "7.0.0")), - .package(url: "https://github.com/Quick/Nimble.git", .upToNextMajor(from: "12.0.0")), - ], targets: [ .target( name: "SimpleKeychain", @@ -20,8 +15,6 @@ let package = Package( name: "SimpleKeychainTests", dependencies: [ "SimpleKeychain", - .product(name: "Quick", package: "Quick"), - .product(name: "Nimble", package: "Nimble"), ], path: "SimpleKeychainTests", exclude: ["Info.plist"]) diff --git a/SimpleKeychain.xcodeproj/project.pbxproj b/SimpleKeychain.xcodeproj/project.pbxproj index 414f00d..11748c6 100644 --- a/SimpleKeychain.xcodeproj/project.pbxproj +++ b/SimpleKeychain.xcodeproj/project.pbxproj @@ -20,8 +20,6 @@ 5C737B34285AB9B100B4BB25 /* SimpleKeychainErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C737B2F285AB57A00B4BB25 /* SimpleKeychainErrorSpec.swift */; }; 5C737B35285AB9B100B4BB25 /* SimpleKeychainErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C737B2F285AB57A00B4BB25 /* SimpleKeychainErrorSpec.swift */; }; 5C737B36285AB9B100B4BB25 /* SimpleKeychainErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C737B2F285AB57A00B4BB25 /* SimpleKeychainErrorSpec.swift */; }; - 5C737B4F285ACC9F00B4BB25 /* Nimble.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5C737B50285ACC9F00B4BB25 /* Quick.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5C840112285AFF7B00689C01 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C840111285AFF7B00689C01 /* Accessibility.swift */; }; 5C840113285AFF7B00689C01 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C840111285AFF7B00689C01 /* Accessibility.swift */; }; 5C840114285AFF7B00689C01 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C840111285AFF7B00689C01 /* Accessibility.swift */; }; @@ -32,16 +30,6 @@ 5CDF405B2852D88C003840E6 /* SimpleKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDF40592852D88C003840E6 /* SimpleKeychain.swift */; }; 5CDF405C2852D88C003840E6 /* SimpleKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDF40592852D88C003840E6 /* SimpleKeychain.swift */; }; 5CDF405D2852D88C003840E6 /* SimpleKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDF40592852D88C003840E6 /* SimpleKeychain.swift */; }; - 5CE9CA1226FD42B0005A75FA /* Quick.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */; }; - 5CE9CA1326FD42B0005A75FA /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */; }; - 5CE9CA1626FD42F4005A75FA /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */; }; - 5CE9CA1726FD42F4005A75FA /* Quick.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */; }; - 5CE9CA1826FD42FA005A75FA /* Nimble.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5CE9CA1926FD42FA005A75FA /* Quick.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5CE9CA1A26FD430F005A75FA /* Nimble.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */; }; - 5CE9CA1B26FD430F005A75FA /* Quick.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */; }; - 5CE9CA1C26FD4316005A75FA /* Nimble.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5CE9CA1D26FD4316005A75FA /* Quick.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5CE9CA2126FD43F5005A75FA /* SimpleKeychain.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B108AB81EA637B100ED4DD2 /* SimpleKeychain.framework */; }; 5CEB577B285BCE7E00A32A80 /* AccessibilitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEB577A285BCE7E00A32A80 /* AccessibilitySpec.swift */; }; 5CEB577C285BCE7E00A32A80 /* AccessibilitySpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEB577A285BCE7E00A32A80 /* AccessibilitySpec.swift */; }; @@ -122,28 +110,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 5B062A3A1EA63F6100827E79 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 5CE9CA1C26FD4316005A75FA /* Nimble.xcframework in CopyFiles */, - 5CE9CA1D26FD4316005A75FA /* Quick.xcframework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 5C737B4E285ACC9200B4BB25 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 5C737B4F285ACC9F00B4BB25 /* Nimble.xcframework in CopyFiles */, - 5C737B50285ACC9F00B4BB25 /* Quick.xcframework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 5CAF69A826FE57A90045266B /* Copy Files */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -155,28 +121,6 @@ name = "Copy Files"; runOnlyForDeploymentPostprocessing = 0; }; - 5F4D279C1BCEA6A7003C27B3 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 5CE9CA1826FD42FA005A75FA /* Nimble.xcframework in CopyFiles */, - 5CE9CA1926FD42FA005A75FA /* Quick.xcframework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - C1D1FBAA2C21915F008E9E3F /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - C1D1FBAB2C219170008E9E3F /* Nimble.xcframework in CopyFiles */, - C1D1FBAC2C219170008E9E3F /* Quick.xcframework in CopyFiles */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -196,8 +140,6 @@ 5C737B2F285AB57A00B4BB25 /* SimpleKeychainErrorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleKeychainErrorSpec.swift; sourceTree = ""; }; 5C840111285AFF7B00689C01 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; 5CDF40592852D88C003840E6 /* SimpleKeychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleKeychain.swift; sourceTree = ""; }; - 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Quick.xcframework; path = Carthage/Build/Quick.xcframework; sourceTree = SOURCE_ROOT; }; - 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Nimble.xcframework; path = Carthage/Build/Nimble.xcframework; sourceTree = SOURCE_ROOT; }; 5CEB577A285BCE7E00A32A80 /* AccessibilitySpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilitySpec.swift; sourceTree = ""; }; 5F4D27651BCE995C003C27B3 /* SimpleKeychainSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleKeychainSpec.swift; sourceTree = ""; }; 5F4D277B1BCE99DF003C27B3 /* SimpleKeychainTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleKeychainTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -225,8 +167,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5CE9CA1A26FD430F005A75FA /* Nimble.xcframework in Frameworks */, - 5CE9CA1B26FD430F005A75FA /* Quick.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -256,9 +196,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5CE9CA1326FD42B0005A75FA /* Nimble.xcframework in Frameworks */, - 5CE9CA1226FD42B0005A75FA /* Quick.xcframework in Frameworks */, - C1D1FBC12C21C89C008E9E3F /* CwlPreconditionTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -266,9 +203,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5CE9CA1626FD42F4005A75FA /* Nimble.xcframework in Frameworks */, - 5CE9CA1726FD42F4005A75FA /* Quick.xcframework in Frameworks */, - C1D1FBBF2C21C814008E9E3F /* CwlPreconditionTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -338,8 +272,6 @@ 5F51D71D1BCDC4D400613162 /* Frameworks */ = { isa = PBXGroup; children = ( - 5CE9CA1126FD42B0005A75FA /* Nimble.xcframework */, - 5CE9CA1026FD42B0005A75FA /* Quick.xcframework */, ); name = Frameworks; path = SimpleKeychain; @@ -494,7 +426,6 @@ 5B0D47551EA63C74009FF1BF /* Sources */, 5B0D47561EA63C74009FF1BF /* Frameworks */, 5B0D47571EA63C74009FF1BF /* Resources */, - 5B062A3A1EA63F6100827E79 /* CopyFiles */, ); buildRules = ( ); @@ -569,7 +500,6 @@ 5F4D27771BCE99DF003C27B3 /* Sources */, 5F4D27781BCE99DF003C27B3 /* Frameworks */, 5F4D27791BCE99DF003C27B3 /* Resources */, - 5C737B4E285ACC9200B4BB25 /* CopyFiles */, ); buildRules = ( ); @@ -592,7 +522,6 @@ 5F4D278B1BCEA69E003C27B3 /* Sources */, 5F4D278C1BCEA69E003C27B3 /* Frameworks */, 5F4D278D1BCEA69E003C27B3 /* Resources */, - 5F4D279C1BCEA6A7003C27B3 /* CopyFiles */, ); buildRules = ( ); @@ -1196,7 +1125,6 @@ 5B0D47621EA63C74009FF1BF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -1224,7 +1152,6 @@ 5B0D47631EA63C74009FF1BF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -1430,7 +1357,6 @@ 5F4D27831BCE99E0003C27B3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; DEBUG_INFORMATION_FORMAT = dwarf; INFOPLIST_FILE = SimpleKeychainTests/Info.plist; @@ -1451,7 +1377,6 @@ 5F4D27841BCE99E0003C27B3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = SimpleKeychainTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; diff --git a/SimpleKeychainTests/AccessibilitySpec.swift b/SimpleKeychainTests/AccessibilitySpec.swift index 8faea48..c078cc8 100644 --- a/SimpleKeychainTests/AccessibilitySpec.swift +++ b/SimpleKeychainTests/AccessibilitySpec.swift @@ -1,74 +1,63 @@ import Security -import Nimble -import Quick +import XCTest import SimpleKeychain -class AccessibilitySpec: QuickSpec { - override class func spec() { - describe("raw representable") { - context("from raw value to case") { - it("should map kSecAttrAccessibleWhenUnlocked") { - let sut = Accessibility(rawValue: kSecAttrAccessibleWhenUnlocked) - expect(sut) == Accessibility.whenUnlocked - } - - it("should map kSecAttrAccessibleWhenUnlockedThisDeviceOnly") { - let sut = Accessibility(rawValue: kSecAttrAccessibleWhenUnlockedThisDeviceOnly) - expect(sut) == Accessibility.whenUnlockedThisDeviceOnly - } - - it("should map kSecAttrAccessibleAfterFirstUnlock") { - let sut = Accessibility(rawValue: kSecAttrAccessibleAfterFirstUnlock) - expect(sut) == Accessibility.afterFirstUnlock - } - - it("should map kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly") { - let sut = Accessibility(rawValue: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) - expect(sut) == Accessibility.afterFirstUnlockThisDeviceOnly - } - - it("should map kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly") { - let sut = Accessibility(rawValue: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) - expect(sut) == Accessibility.whenPasscodeSetThisDeviceOnly - } - - it("should map unknown values") { - let sut = Accessibility(rawValue: "foo" as CFString) - expect(sut) == Accessibility.afterFirstUnlock - } - } - - context("from case to raw value") { - it("should map whenUnlocked") { - let sut = Accessibility.whenUnlocked.rawValue as String - expect(sut) == (kSecAttrAccessibleWhenUnlocked as String) - } - - it("should map whenUnlockedThisDeviceOnly") { - let sut = Accessibility.whenUnlockedThisDeviceOnly.rawValue as String - expect(sut) == (kSecAttrAccessibleWhenUnlockedThisDeviceOnly as String) - } - - it("should map afterFirstUnlock") { - let sut = Accessibility.afterFirstUnlock.rawValue as String - expect(sut) == (kSecAttrAccessibleAfterFirstUnlock as String) - } - - it("should map afterFirstUnlockThisDeviceOnly") { - let sut = Accessibility.afterFirstUnlockThisDeviceOnly.rawValue as String - expect(sut) == (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String) - } - - it("should map whenPasscodeSetThisDeviceOnly") { - let sut = Accessibility.whenPasscodeSetThisDeviceOnly.rawValue as String - expect(sut) == (kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly as String) - } - - it("should map whenPasscodeSetThisDeviceOnly") { - let sut = Accessibility.whenPasscodeSetThisDeviceOnly.rawValue as String - expect(sut) == (kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly as String) - } - } - } +class AccessibilitySpec: XCTestCase { + + // Test from raw value to case + func testKSecAttrAccessibleWhenUnlocked() { + let sut = Accessibility(rawValue: kSecAttrAccessibleWhenUnlocked) + XCTAssertEqual(sut, Accessibility.whenUnlocked) + } + + func testKSecAttrAccessibleWhenUnlockedThisDeviceOnly() { + let sut = Accessibility(rawValue: kSecAttrAccessibleWhenUnlockedThisDeviceOnly) + XCTAssertEqual(sut, Accessibility.whenUnlockedThisDeviceOnly) + } + + func testKSecAttrAccessibleAfterFirstUnlock() { + let sut = Accessibility(rawValue: kSecAttrAccessibleAfterFirstUnlock) + XCTAssertEqual(sut, Accessibility.afterFirstUnlock) + } + + func testKSecAttrAccessibleAfterFirstUnlockThisDeviceOnly() { + let sut = Accessibility(rawValue: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) + XCTAssertEqual(sut, Accessibility.afterFirstUnlockThisDeviceOnly) + } + + func testKSecAttrAccessibleWhenPasscodeSetThisDeviceOnly() { + let sut = Accessibility(rawValue: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) + XCTAssertEqual(sut, Accessibility.whenPasscodeSetThisDeviceOnly) + } + + func testUnknownValues() { + let sut = Accessibility(rawValue: "foo" as CFString) + XCTAssertEqual(sut, Accessibility.afterFirstUnlock) + } + + // Test from case to raw value + func testWhenUnlocked() { + let sut = Accessibility.whenUnlocked.rawValue as String + XCTAssertEqual(sut, kSecAttrAccessibleWhenUnlocked as String) + } + + func testWhenUnlockedThisDeviceOnly() { + let sut = Accessibility.whenUnlockedThisDeviceOnly.rawValue as String + XCTAssertEqual(sut, kSecAttrAccessibleWhenUnlockedThisDeviceOnly as String) + } + + func testAfterFirstUnlock() { + let sut = Accessibility.afterFirstUnlock.rawValue as String + XCTAssertEqual(sut, kSecAttrAccessibleAfterFirstUnlock as String) + } + + func testAfterFirstUnlockThisDeviceOnly() { + let sut = Accessibility.afterFirstUnlockThisDeviceOnly.rawValue as String + XCTAssertEqual(sut, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String) + } + + func testWhenPasscodeSetThisDeviceOnly() { + let sut = Accessibility.whenPasscodeSetThisDeviceOnly.rawValue as String + XCTAssertEqual(sut, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly as String) } } diff --git a/SimpleKeychainTests/SimpleKeychainErrorSpec.swift b/SimpleKeychainTests/SimpleKeychainErrorSpec.swift index 69fe044..c9ecf67 100644 --- a/SimpleKeychainTests/SimpleKeychainErrorSpec.swift +++ b/SimpleKeychainTests/SimpleKeychainErrorSpec.swift @@ -1,234 +1,217 @@ import Foundation import Security -import Nimble -import Quick +import XCTest @testable import SimpleKeychain -class SimpleKeychainErrorSpec: QuickSpec { - override class func spec() { - describe("init") { - it("should initialize with code") { - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut.code) == SimpleKeychainError.Code.operationNotImplemented - } - } - - describe("operators") { - it("should be equal by code") { - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut) == SimpleKeychainError.operationNotImplemented - } - - it("should not be equal to an error with a different code") { - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut) != SimpleKeychainError.itemNotAvailable - } - - it("should not be equal to an error with a different description") { - let sut = SimpleKeychainError(code: .unknown(message: "foo")) - expect(sut) != SimpleKeychainError(code: .unknown(message: "bar")) - } - - it("should pattern match by code") { - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut ~= SimpleKeychainError.operationNotImplemented) == true - } - - it("should not pattern match by code with a different error") { - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut ~= SimpleKeychainError.itemNotAvailable) == false - } - - it("should pattern match by code with a generic error") { - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut ~= (SimpleKeychainError.operationNotImplemented) as Error) == true - } - - it("should not pattern match by code with a different generic error") { - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut ~= NSError()) == false - } - } - - describe("debug description") { - it("should match the localized message") { - let sut = SimpleKeychainError(code: .itemNotAvailable) - expect(sut.debugDescription) == SimpleKeychainError.itemNotAvailable.debugDescription - } - - it("should match the error description") { - let sut = SimpleKeychainError(code: .itemNotAvailable) - expect(sut.debugDescription) == SimpleKeychainError.itemNotAvailable.errorDescription - } - } - - describe("error message") { - it("should return message for operation not implemented") { - let message = "errSecUnimplemented: A function or operation is not implemented." - let sut = SimpleKeychainError(code: .operationNotImplemented) - expect(sut.localizedDescription) == message - } - - it("should return message for invalid parameters") { - let message = "errSecParam: One or more parameters passed to the function are not valid." - let sut = SimpleKeychainError(code: .invalidParameters) - expect(sut.localizedDescription) == message - } - - it("should return message for user canceled") { - let message = "errSecUserCanceled: User canceled the operation." - let sut = SimpleKeychainError(code: .userCanceled) - expect(sut.localizedDescription) == message - } - - it("should return message for item not available") { - let message = "errSecNotAvailable: No trust results are available." - let sut = SimpleKeychainError(code: .itemNotAvailable) - expect(sut.localizedDescription) == message - } - - it("should return message for auth failed") { - let message = "errSecAuthFailed: Authorization and/or authentication failed." - let sut = SimpleKeychainError(code: .authFailed) - expect(sut.localizedDescription) == message - } - - it("should return message for duplicate item") { - let message = "errSecDuplicateItem: The item already exists." - let sut = SimpleKeychainError(code: .duplicateItem) - expect(sut.localizedDescription) == message - } - - it("should return message for item not found") { - let message = "errSecItemNotFound: The item cannot be found." - let sut = SimpleKeychainError(code: .itemNotFound) - expect(sut.localizedDescription) == message - } - - it("should return message for interaction not allowed") { - let message = "errSecInteractionNotAllowed: Interaction with the Security Server is not allowed." - let sut = SimpleKeychainError(code: .interactionNotAllowed) - expect(sut.localizedDescription) == message - } - - it("should return message for decode failed") { - let message = "errSecDecode: Unable to decode the provided data." - let sut = SimpleKeychainError(code: .decodeFailed) - expect(sut.localizedDescription) == message - } - - it("should return message for other error") { - let status: OSStatus = 123 - let message = "Unspecified Keychain error: \(status)." - let sut = SimpleKeychainError(code: .other(status: status)) - expect(sut.localizedDescription) == message - } - - it("should return message for unknown error") { - let description = "foo" - let message = "Unknown error: \(description)." - let sut = SimpleKeychainError(code: .unknown(message: description)) - expect(sut.localizedDescription) == message - } - } - - describe("code") { - context("from status to code") { - it("should map errSecUnimplemented") { - let sut = SimpleKeychainError.Code(rawValue: errSecUnimplemented) - expect(sut) == SimpleKeychainError.operationNotImplemented.code - } - - it("should map errSecParam") { - let sut = SimpleKeychainError.Code(rawValue: errSecParam) - expect(sut) == SimpleKeychainError.invalidParameters.code - } - - it("should map errSecUserCanceled") { - let sut = SimpleKeychainError.Code(rawValue: errSecUserCanceled) - expect(sut) == SimpleKeychainError.userCanceled.code - } - - it("should map errSecNotAvailable") { - let sut = SimpleKeychainError.Code(rawValue: errSecNotAvailable) - expect(sut) == SimpleKeychainError.itemNotAvailable.code - } - - it("should map errSecAuthFailed") { - let sut = SimpleKeychainError.Code(rawValue: errSecAuthFailed) - expect(sut) == SimpleKeychainError.authFailed.code - } - - it("should map errSecDuplicateItem") { - let sut = SimpleKeychainError.Code(rawValue: errSecDuplicateItem) - expect(sut) == SimpleKeychainError.duplicateItem.code - } - - it("should map errSecItemNotFound") { - let sut = SimpleKeychainError.Code(rawValue: errSecItemNotFound) - expect(sut) == SimpleKeychainError.itemNotFound.code - } - - it("should map errSecInteractionNotAllowed") { - let sut = SimpleKeychainError.Code(rawValue: errSecInteractionNotAllowed) - expect(sut) == SimpleKeychainError.interactionNotAllowed.code - } - - it("should map errSecDecode") { - let sut = SimpleKeychainError.Code(rawValue: errSecDecode) - expect(sut) == SimpleKeychainError.decodeFailed.code - } - - it("should map other status value") { - let status: OSStatus = 1234 - let sut = SimpleKeychainError.Code(rawValue: status) - expect(sut.rawValue) == status - } - } - - context("from code to status") { - it("should map operationNotImplemented") { - expect(SimpleKeychainError.operationNotImplemented.code.rawValue) == errSecUnimplemented - } - - it("should map invalidParameters") { - expect(SimpleKeychainError.invalidParameters.code.rawValue) == errSecParam - } - - it("should map userCanceled") { - expect(SimpleKeychainError.userCanceled.code.rawValue) == errSecUserCanceled - } - - it("should map itemNotAvailable") { - expect(SimpleKeychainError.itemNotAvailable.code.rawValue) == errSecNotAvailable - } - - it("should map authFailed") { - expect(SimpleKeychainError.authFailed.code.rawValue) == errSecAuthFailed - } - - it("should map duplicateItem") { - expect(SimpleKeychainError.duplicateItem.code.rawValue) == errSecDuplicateItem - } - - it("should map interactionNotAllowed") { - expect(SimpleKeychainError.interactionNotAllowed.code.rawValue) == errSecInteractionNotAllowed - } - - it("should map decodeFailed") { - expect(SimpleKeychainError.decodeFailed.code.rawValue) == errSecDecode - } - - it("should map other") { - let status: OSStatus = 1234 - expect(SimpleKeychainError(code: .other(status: status)).status ) == status - } - - it("should map unknown") { - expect(SimpleKeychainError.unknown.code.rawValue) == errSecSuccess - } - } - } +class SimpleKeychainErrorSpec: XCTestCase { + func testInit() { + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertEqual(sut.code, SimpleKeychainError.Code.operationNotImplemented) + } + + func testOperators_shouldBeEqualByCode() { + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertEqual(sut, SimpleKeychainError.operationNotImplemented) + } + + func testOperators_shouldNotBeEqualToErrorWithDifferentCode() { + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertNotEqual(sut, SimpleKeychainError.itemNotAvailable) + } + + func testOperators_shouldNotBeEqualToErrorWithDifferentDescription() { + let sut = SimpleKeychainError(code: .unknown(message: "foo")) + XCTAssertNotEqual(sut, SimpleKeychainError(code: .unknown(message: "bar"))) + } + + func testOperators_shouldPatternMatchByCode() { + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertTrue(sut ~= SimpleKeychainError.operationNotImplemented) + } + + func testOperators_shouldNotPatternMatchByCodeWithDifferentError() { + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertFalse(sut ~= SimpleKeychainError.itemNotAvailable) + } + + func testOperators_shouldPatternMatchByCodeWithGenericError() { + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertTrue(sut ~= (SimpleKeychainError.operationNotImplemented) as Error) + } + + func testOperators_shouldNotPatternMatchByCodeWithDifferentGenericError() { + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertFalse(sut ~= NSError()) + } + + func testDebugDescription_shouldMatchLocalizedMessage() { + let sut = SimpleKeychainError(code: .itemNotAvailable) + XCTAssertEqual(sut.debugDescription, SimpleKeychainError.itemNotAvailable.debugDescription) + } + + func testDebugDescription_shouldMatchErrorDescription() { + let sut = SimpleKeychainError(code: .itemNotAvailable) + XCTAssertEqual(sut.debugDescription, SimpleKeychainError.itemNotAvailable.errorDescription) + } + + func testErrorMessage_shouldReturnMessageForOperationNotImplemented() { + let message = "errSecUnimplemented: A function or operation is not implemented." + let sut = SimpleKeychainError(code: .operationNotImplemented) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForInvalidParameters() { + let message = "errSecParam: One or more parameters passed to the function are not valid." + let sut = SimpleKeychainError(code: .invalidParameters) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForUserCanceled() { + let message = "errSecUserCanceled: User canceled the operation." + let sut = SimpleKeychainError(code: .userCanceled) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForItemNotAvailable() { + let message = "errSecNotAvailable: No trust results are available." + let sut = SimpleKeychainError(code: .itemNotAvailable) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForAuthFailed() { + let message = "errSecAuthFailed: Authorization and/or authentication failed." + let sut = SimpleKeychainError(code: .authFailed) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForDuplicateItem() { + let message = "errSecDuplicateItem: The item already exists." + let sut = SimpleKeychainError(code: .duplicateItem) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForItemNotFound() { + let message = "errSecItemNotFound: The item cannot be found." + let sut = SimpleKeychainError(code: .itemNotFound) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForInteractionNotAllowed() { + let message = "errSecInteractionNotAllowed: Interaction with the Security Server is not allowed." + let sut = SimpleKeychainError(code: .interactionNotAllowed) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForDecodeFailed() { + let message = "errSecDecode: Unable to decode the provided data." + let sut = SimpleKeychainError(code: .decodeFailed) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForOtherError() { + let status: OSStatus = 123 + let message = "Unspecified Keychain error: \(status)." + let sut = SimpleKeychainError(code: .other(status: status)) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testErrorMessage_shouldReturnMessageForUnknownError() { + let description = "foo" + let message = "Unknown error: \(description)." + let sut = SimpleKeychainError(code: .unknown(message: description)) + XCTAssertEqual(sut.localizedDescription, message) + } + + func testMapErrSecUnimplemented() { + let sut = SimpleKeychainError.Code(rawValue: errSecUnimplemented) + XCTAssertEqual(sut, SimpleKeychainError.operationNotImplemented.code) + } + + func testMapErrSecParam() { + let sut = SimpleKeychainError.Code(rawValue: errSecParam) + XCTAssertEqual(sut, SimpleKeychainError.invalidParameters.code) + } + + func testMapErrSecUserCanceled() { + let sut = SimpleKeychainError.Code(rawValue: errSecUserCanceled) + XCTAssertEqual(sut, SimpleKeychainError.userCanceled.code) + } + + func testMapErrSecNotAvailable() { + let sut = SimpleKeychainError.Code(rawValue: errSecNotAvailable) + XCTAssertEqual(sut, SimpleKeychainError.itemNotAvailable.code) + } + + func testMapErrSecAuthFailed() { + let sut = SimpleKeychainError.Code(rawValue: errSecAuthFailed) + XCTAssertEqual(sut, SimpleKeychainError.authFailed.code) + } + + func testMapErrSecDuplicateItem() { + let sut = SimpleKeychainError.Code(rawValue: errSecDuplicateItem) + XCTAssertEqual(sut, SimpleKeychainError.duplicateItem.code) + } + + func testMapErrSecItemNotFound() { + let sut = SimpleKeychainError.Code(rawValue: errSecItemNotFound) + XCTAssertEqual(sut, SimpleKeychainError.itemNotFound.code) + } + + func testMapErrSecInteractionNotAllowed() { + let sut = SimpleKeychainError.Code(rawValue: errSecInteractionNotAllowed) + XCTAssertEqual(sut, SimpleKeychainError.interactionNotAllowed.code) + } + + func testMapErrSecDecode() { + let sut = SimpleKeychainError.Code(rawValue: errSecDecode) + XCTAssertEqual(sut, SimpleKeychainError.decodeFailed.code) + } + + func testMapOtherStatusValue() { + let status: OSStatus = 1234 + let sut = SimpleKeychainError.Code(rawValue: status) + XCTAssertEqual(sut.rawValue, status) + } + + func testMapOperationNotImplemented() { + XCTAssertEqual(SimpleKeychainError.operationNotImplemented.code.rawValue, errSecUnimplemented) + } + + func testMapInvalidParameters() { + XCTAssertEqual(SimpleKeychainError.invalidParameters.code.rawValue, errSecParam) + } + + func testMapUserCanceled() { + XCTAssertEqual(SimpleKeychainError.userCanceled.code.rawValue, errSecUserCanceled) + } + + func testMapItemNotAvailable() { + XCTAssertEqual(SimpleKeychainError.itemNotAvailable.code.rawValue, errSecNotAvailable) + } + + func testMapAuthFailed() { + XCTAssertEqual(SimpleKeychainError.authFailed.code.rawValue, errSecAuthFailed) + } + + func testMapDuplicateItem() { + XCTAssertEqual(SimpleKeychainError.duplicateItem.code.rawValue, errSecDuplicateItem) + } + + func testMapInteractionNotAllowed() { + XCTAssertEqual(SimpleKeychainError.interactionNotAllowed.code.rawValue, errSecInteractionNotAllowed) + } + + func testMapDecodeFailed() { + XCTAssertEqual(SimpleKeychainError.decodeFailed.code.rawValue, errSecDecode) + } + + func testMapOther() { + let status: OSStatus = 1234 + XCTAssertEqual(SimpleKeychainError(code: .other(status: status)).status, status) + } + + func testMapUnknown() { + XCTAssertEqual(SimpleKeychainError.unknown.code.rawValue, errSecSuccess) } } diff --git a/SimpleKeychainTests/SimpleKeychainSpec.swift b/SimpleKeychainTests/SimpleKeychainSpec.swift index 36d8137..c167430 100644 --- a/SimpleKeychainTests/SimpleKeychainSpec.swift +++ b/SimpleKeychainTests/SimpleKeychainSpec.swift @@ -1,6 +1,5 @@ +import XCTest import LocalAuthentication -import Nimble -import Quick @testable import SimpleKeychain @@ -8,398 +7,371 @@ let PublicKeyTag = "public" let PrivateKeyTag = "private" let KeychainService = "com.auth0.simplekeychain.tests" -class SimpleKeychainSpec: AsyncSpec { - override class func spec() { - describe("SimpleKeychain") { - var sut: SimpleKeychain! - - afterEach { - try? sut.deleteAll() - } - - describe("initialization") { - it("should init with default values") { - sut = SimpleKeychain() - expect(sut.accessGroup).to(beNil()) - expect(sut.service) == Bundle.main.bundleIdentifier - expect(sut.accessibility) == Accessibility.afterFirstUnlock - expect(sut.accessControlFlags).to(beNil()) - expect(sut.isSynchronizable) == false - expect(sut.attributes).to(beEmpty()) - } - - it("should init with custom values") { - sut = SimpleKeychain(service: KeychainService, - accessGroup: "Group", - accessibility: .whenUnlocked, - accessControlFlags: .userPresence, - synchronizable: true, - attributes: ["foo": "bar"]) - expect(sut.accessGroup) == "Group" - expect(sut.service) == KeychainService - expect(sut.accessibility) == Accessibility.whenUnlocked - expect(sut.accessControlFlags) == .userPresence - expect(sut.isSynchronizable) == true - expect(sut.attributes.count) == 1 - expect(sut.attributes["foo"] as? String) == "bar" - } - - #if canImport(LocalAuthentication) && !os(tvOS) - it("should init with custom local authentication context") { - let context = LAContext() - sut = SimpleKeychain(context: context) - expect(sut.context).to(be(context)) - } - #endif - } - - describe("storing items") { - var key: String! - - beforeEach({ - sut = SimpleKeychain(service: KeychainService) - key = UUID().uuidString - }) - - context("string items") { - it("should store a string item under a new key") { - expect(try sut.set("foo", forKey: key)).toNot(throwError()) - } - - it("should store a string item under an existing key") { - try sut.set("foo", forKey: key) - expect(try sut.set("bar", forKey: key)).toNot(throwError()) - } - } - - context("data items") { - it("should store a data item under a new key") { - expect(try sut.set(Data(), forKey: key)).toNot(throwError()) - } - - it("should store a data item under an existing key") { - try sut.set( Data(), forKey: key) - expect(try sut.set(Data(), forKey: key)).toNot(throwError()) - } - } - } - - describe("removing items") { - var key: String! - - beforeEach { - sut = SimpleKeychain(service: KeychainService) - key = UUID().uuidString - try! sut.set("foo", forKey: key) - } - - it("should delete item") { - expect(try sut.deleteItem(forKey: key)).toNot(throwError()) - } - - it("should throw an error when deleting an item with a non-existing key") { - expect(try sut.deleteItem(forKey: "SHOULDNOTEXIST")).to(throwError()) - } - - it("should delete all items") { - try sut.deleteAll() - expect(try sut.string(forKey: key)).to(throwError(SimpleKeychainError.itemNotFound)) - } - - #if os(macOS) - it("should include limit all attribute when deleting all items") { - var limit: String? - sut.remove = { query in - let key = kSecMatchLimit as String - limit = (query as NSDictionary).value(forKey: key) as? String - return errSecSuccess - } - try sut.deleteAll() - expect(limit).toEventually(equal(kSecMatchLimitAll as String)) - } - #else - it("should not include limit all attribute when deleting all items") { - var limit: String? = "" - sut.remove = { query in - let key = kSecMatchLimit as String - limit = (query as NSDictionary).value(forKey: key) as? String - return errSecSuccess - } - try sut.deleteAll() - await expect(limit).toEventually(beNil()) - } - #endif - } - - describe("retrieving items") { - var key: String! - - beforeEach { - sut = SimpleKeychain(service: KeychainService) - key = UUID().uuidString - try! sut.set("foo", forKey: key) - } - - it("should retrieve string item") { - expect(try sut.string(forKey: key)) == "foo" - } - - it("should retrieve data item") { - expect(try sut.data(forKey: key)).notTo(beNil()) - } - - it("should throw error when retrieving a string item with non-existing key") { - let expectedError = SimpleKeychainError.itemNotFound - expect(try sut.string(forKey: "SHOULDNOTEXIST")).to(throwError(expectedError)) - } - - it("should throw error when retrieving a data item with non-existing key") { - let expectedError = SimpleKeychainError.itemNotFound - expect(try sut.data(forKey: "SHOULDNOTEXIST")).to(throwError(expectedError)) - } - - it("should throw an error when retrieving a string item that cannot be decoded") { - let message = "Unable to convert the retrieved item to a String value" - let expectedError = SimpleKeychainError(code: .unknown(message: message)) - sut.retrieve = { _, result in - result?.pointee = .some(NSData(data: withUnsafeBytes(of: Date()) { Data($0) })) - return errSecSuccess - } - expect(try sut.string(forKey: key)).to(throwError(expectedError)) - } - - it("should throw an error when retrieving an invalid data item") { - let message = "Unable to cast the retrieved item to a Data value" - let expectedError = SimpleKeychainError(code: .unknown(message: message)) - sut.retrieve = { _, result in - result?.pointee = .some(NSDate()) - return errSecSuccess - } - expect(try sut.string(forKey: key)).to(throwError(expectedError)) - } - } - - describe("checking items") { - var key: String! - - beforeEach { - sut = SimpleKeychain(service: KeychainService) - key = UUID().uuidString - try! sut.set("foo", forKey: key) - } - - it("should return true when the item is stored") { - expect(try sut.hasItem(forKey: key)) == true - } - - it("should return false when the item is not stored") { - expect(try sut.hasItem(forKey: "SHOULDNOTEXIST")) == false - } - } - - describe("retrieving keys") { - var keys: [String] = [] - - beforeEach { - try! sut.deleteAll() - sut = SimpleKeychain(service: KeychainService) - keys.append(UUID().uuidString) - keys.append(UUID().uuidString) - keys.append(UUID().uuidString) - for (i, key) in keys.enumerated() { - try! sut.set("foo\(i)", forKey: key) - } - } - - it("should return all the keys") { - expect(try sut.keys()) == keys - } - - it("should return an empty array when there are no keys") { - for key in keys { - expect(try sut.data(forKey: key)).notTo(beNil()) - } - expect(try sut.keys().count) == keys.count - try sut.deleteAll() - let expectedError = SimpleKeychainError.itemNotFound - for key in keys { - expect(try sut.data(forKey: key)).to(throwError(expectedError)) - } - expect(try sut.keys().count) == 0 - } - - it("should throw an error when retrieving invalid attributes") { - let message = "Unable to cast the retrieved items to a [[String: Any]] value" - let expectedError = SimpleKeychainError(code: .unknown(message: message)) - sut.retrieve = { _, result in - result?.pointee = .some(NSDate()) - return errSecSuccess - } - expect(try sut.keys()).to(throwError(expectedError)) - } - } - - describe("queries") { - beforeEach { - sut = SimpleKeychain(service: KeychainService) - } - - context("base query") { - it("should contain default attributes") { - let query = sut.baseQuery() - expect((query[kSecClass as String] as? String)) == kSecClassGenericPassword as String - expect((query[kSecAttrService as String] as? String)) == sut.service - expect((query[kSecAttrAccount as String] as? String)).to(beNil()) - expect((query[kSecValueData as String] as? Data)).to(beNil()) - expect((query[kSecAttrAccessGroup as String] as? String)).to(beNil()) - expect((query[kSecAttrSynchronizable as String] as? Bool)).to(beNil()) - #if canImport(LocalAuthentication) && !os(tvOS) - expect((query[kSecUseAuthenticationContext as String] as? LAContext)).to(beNil()) - #endif - } - - it("should include additional attributes") { - let key = "foo" - let value = "bar" - sut = SimpleKeychain(attributes: [key: value]) - let query = sut.baseQuery() - expect((query[key] as? String)) == value - } - - it("should supersede additional attributes") { - let key = kSecAttrService as String - let value = "foo" - sut = SimpleKeychain(attributes: [key: value]) - let query = sut.baseQuery() - expect((query[key] as? String)) == sut.service - } - - it("should include account attribute") { - let key = "foo" - let query = sut.baseQuery(withKey: key) - expect((query[kSecAttrAccount as String] as? String)) == key - } - - it("should include data attribute") { - let data = Data() - let query = sut.baseQuery(data: data) - expect((query[kSecValueData as String] as? Data)) == data - } - - it("should include access group attribute") { - sut = SimpleKeychain(accessGroup: "foo") - let query = sut.baseQuery() - expect((query[kSecAttrAccessGroup as String] as? String)) == sut.accessGroup - } - - it("should include synchronizable attribute") { - sut = SimpleKeychain(synchronizable: true) - let query = sut.baseQuery() - expect((query[kSecAttrSynchronizable as String] as? Bool)) == sut.isSynchronizable - } - - #if canImport(LocalAuthentication) && !os(tvOS) - it("should include context attribute") { - sut = SimpleKeychain(context: LAContext()) - let query = sut.baseQuery() - expect((query[kSecUseAuthenticationContext as String] as? LAContext)) == sut.context - } - #endif - } - - context("get all query") { - it("should contain the base query") { - expect(sut.getAllQuery).to(containBaseQuery(sut.baseQuery())) - } - - it("should contain return attribute") { - let query = sut.getAllQuery - expect((query[kSecReturnAttributes as String] as? Bool)) == true - } - - it("should contain limit attribute") { - let query = sut.getAllQuery - expect((query[kSecMatchLimit as String] as? String)) == kSecMatchLimitAll as String - } - } - - context("get one query") { - it("should contain the base query") { - let key = "foo" - expect(sut.getOneQuery(byKey: key)).to(containBaseQuery(sut.baseQuery(withKey: key))) - } - - it("should contain data attribute") { - let query = sut.getOneQuery(byKey: "foo") - expect((query[kSecReturnData as String] as? Bool)) == true - } - - it("should contain limit attribute") { - let query = sut.getOneQuery(byKey: "foo") - expect((query[kSecMatchLimit as String] as? String)) == kSecMatchLimitOne as String - } - } - - context("set query") { - it("should contain the base query") { - let key = "foo" - let data = Data() - let query = sut.setQuery(forKey: key, data: data) - let baseQuery = sut.baseQuery(withKey: key, data: data) - expect(query).to(containBaseQuery(baseQuery)) - } - - it("should include access control attribute") { - sut = SimpleKeychain(accessControlFlags: .userPresence) - let query = sut.setQuery(forKey: "foo", data: Data()) - expect(query[kSecAttrAccessControl as String]).toNot(beNil()) - } - - #if os(macOS) - it("should not include accessibility attribute by default") { - let query = sut.setQuery(forKey: "foo", data: Data()) - expect((query[kSecAttrAccessible as String] as? String)).to(beNil()) - } - - it("should include accessibility attribute when iCloud sharing is enabled") { - sut = SimpleKeychain(synchronizable: true) - let query = sut.setQuery(forKey: "foo", data: Data()) - let expectedAccessibility = sut.accessibility.rawValue as String - expect((query[kSecAttrAccessible as String] as? String)) == expectedAccessibility - } - - it("should include accessibility attribute when data protection is enabled") { - let attributes = [kSecUseDataProtectionKeychain as String: kCFBooleanTrue as Any] - sut = SimpleKeychain(attributes: attributes) - let query = sut.setQuery(forKey: "foo", data: Data()) - let expectedAccessibility = sut.accessibility.rawValue as String - expect((query[kSecAttrAccessible as String] as? String)) == expectedAccessibility - } - #else - it("should include accessibility attribute") { - let query = sut.setQuery(forKey: "foo", data: Data()) - let expectedAccessibility = sut.accessibility.rawValue as String - expect((query[kSecAttrAccessible as String] as? String)) == expectedAccessibility - } - #endif - } +class SimpleKeychainTests: XCTestCase { + var sut: SimpleKeychain! + + override func setUp() { + super.setUp() + sut = SimpleKeychain() + } + + override func tearDown() { + try? sut.deleteAll() + sut = nil + super.tearDown() + } + + func testInitializationWithDefaultValues() { + XCTAssertEqual(sut.accessGroup, nil) + XCTAssertEqual(sut.service, Bundle.main.bundleIdentifier) + XCTAssertEqual(sut.accessibility, Accessibility.afterFirstUnlock) + XCTAssertEqual(sut.accessControlFlags, nil) + XCTAssertEqual(sut.isSynchronizable, false) + XCTAssertTrue(sut.attributes.isEmpty) + } + + func testInitializationWithCustomValues() { + sut = SimpleKeychain(service: KeychainService, + accessGroup: "Group", + accessibility: .whenUnlocked, + accessControlFlags: .userPresence, + synchronizable: true, + attributes: ["foo": "bar"]) + + XCTAssertEqual(sut.accessGroup, "Group") + XCTAssertEqual(sut.service, KeychainService) + XCTAssertEqual(sut.accessibility, Accessibility.whenUnlocked) + XCTAssertEqual(sut.accessControlFlags, .userPresence) + XCTAssertEqual(sut.isSynchronizable, true) + XCTAssertEqual(sut.attributes.count, 1) + XCTAssertEqual(sut.attributes["foo"] as? String, "bar") + } + + #if canImport(LocalAuthentication) && !os(tvOS) + func testInitializationWithCustomLocalAuthenticationContext() { + let context = LAContext() + sut = SimpleKeychain(context: context) + XCTAssertEqual(sut.context, context) + } + #endif + + func testStoringStringItemUnderNewKey() { + let key = UUID().uuidString + XCTAssertNoThrow(try sut.set("foo", forKey: key)) + } + + func testStoringStringItemUnderExistingKey() { + let key = UUID().uuidString + try? sut.set("foo", forKey: key) + XCTAssertNoThrow(try sut.set("bar", forKey: key)) + } + + func testStoringDataItemUnderNewKey() { + let key = UUID().uuidString + XCTAssertNoThrow(try sut.set(Data(), forKey: key)) + } + + func testStoringDataItemUnderExistingKey() { + let key = UUID().uuidString + try? sut.set(Data(), forKey: key) + XCTAssertNoThrow(try sut.set(Data(), forKey: key)) + } + + func testDeletingItem() { + let key = UUID().uuidString + try? sut.set("foo", forKey: key) + XCTAssertNoThrow(try sut.deleteItem(forKey: key)) + } + + func testDeletingNonExistingItem() { + XCTAssertThrowsError(try sut.deleteItem(forKey: "SHOULDNOTEXIST")) + } + + func testDeletingAllItems() { + let key = UUID().uuidString + try? sut.set("foo", forKey: key) + try? sut.deleteAll() + XCTAssertThrowsError(try sut.string(forKey: key)) + } + + #if os(macOS) + func testIncludingLimitAllAttributeWhenDeletingAllItems() { + var limit: String? + sut.remove = { query in + let key = kSecMatchLimit as String + limit = (query as NSDictionary).value(forKey: key) as? String + return errSecSuccess + } + try? sut.deleteAll() + XCTAssertEqual(limit, kSecMatchLimitAll as String) + } + #else + func testNotIncludingLimitAllAttributeWhenDeletingAllItems() { + var limit: String? = "" + sut.remove = { query in + let key = kSecMatchLimit as String + limit = (query as NSDictionary).value(forKey: key) as? String + return errSecSuccess + } + try? sut.deleteAll() + XCTAssertNil(limit) + } + #endif + + func testRetrievingStringItem() { + let key = UUID().uuidString + try? sut.set("foo", forKey: key) + XCTAssertEqual(try? sut.string(forKey: key), "foo") + } + + func testRetrievingDataItem() { + let key = UUID().uuidString + try? sut.set("foo", forKey: key) + XCTAssertNotNil(try? sut.data(forKey: key)) + } + + func testRetrievingNonExistingStringItem() { + XCTAssertThrowsError(try sut.string(forKey: "SHOULDNOTEXIST")) { error in + XCTAssertEqual(error as? SimpleKeychainError, .itemNotFound) + } + } + + func testRetrievingNonExistingDataItem() { + XCTAssertThrowsError(try sut.data(forKey: "SHOULDNOTEXIST")) { error in + XCTAssertEqual(error as? SimpleKeychainError, .itemNotFound) + } + } + + func testRetrievingStringItemThatCannotBeDecoded() { + let key = UUID().uuidString + let message = "Unable to convert the retrieved item to a String value" + let expectedError = SimpleKeychainError(code: .unknown(message: message)) + sut.retrieve = { _, result in + result?.pointee = .some(NSData(data: withUnsafeBytes(of: Date()) { Data($0) })) + return errSecSuccess + } + XCTAssertThrowsError(try sut.string(forKey: key)) { error in + XCTAssertEqual(error as? SimpleKeychainError, expectedError) + } + } + + func testRetrievingInvalidDataItem() { + let key = UUID().uuidString + let message = "Unable to cast the retrieved item to a Data value" + let expectedError = SimpleKeychainError(code: .unknown(message: message)) + sut.retrieve = { _, result in + result?.pointee = .some(NSDate()) + return errSecSuccess + } + XCTAssertThrowsError(try sut.string(forKey: key)) { error in + XCTAssertEqual(error as? SimpleKeychainError, expectedError) + } + } + + func testCheckingStoredItem() { + let key = UUID().uuidString + try? sut.set("foo", forKey: key) + XCTAssertTrue(try sut.hasItem(forKey: key)) + } + + func testCheckingNonExistingItem() { + XCTAssertFalse(try sut.hasItem(forKey: "SHOULDNOTEXIST")) + } + + func testRetrievingKeys() { + var keys: [String] = [] + try? sut.deleteAll() + keys.append(UUID().uuidString) + keys.append(UUID().uuidString) + keys.append(UUID().uuidString) + for (i, key) in keys.enumerated() { + try? sut.set("foo\(i)", forKey: key) + } + XCTAssertEqual(try sut.keys(), keys) + } + + func testRetrievingEmptyKeys() { + var keys: [String] = [] + try? sut.deleteAll() + keys.append(UUID().uuidString) + keys.append(UUID().uuidString) + keys.append(UUID().uuidString) + for (i, key) in keys.enumerated() { + try? sut.set("foo\(i)", forKey: key) + } + for key in keys { + XCTAssertNotNil(try? sut.data(forKey: key)) + } + XCTAssertEqual(try sut.keys().count, keys.count) + try? sut.deleteAll() + let expectedError = SimpleKeychainError.itemNotFound + for key in keys { + XCTAssertThrowsError(try sut.data(forKey: key)) { error in + XCTAssertEqual(error as? SimpleKeychainError, expectedError) } } + XCTAssertEqual(try sut.keys().count, 0) + } + + func testRetrievingInvalidAttributes() { + let message = "Unable to cast the retrieved items to a [[String: Any]] value" + let expectedError = SimpleKeychainError(code: .unknown(message: message)) + sut.retrieve = { _, result in + result?.pointee = .some(NSDate()) + return errSecSuccess + } + XCTAssertThrowsError(try sut.keys()) { error in + XCTAssertEqual(error as? SimpleKeychainError, expectedError) + } + } + + func testBaseQueryContainsDefaultAttributes() { + let query = sut.baseQuery() + XCTAssertEqual(query[kSecClass as String] as? String, kSecClassGenericPassword as String) + XCTAssertEqual(query[kSecAttrService as String] as? String, sut.service) + XCTAssertNil(query[kSecAttrAccount as String] as? String) + XCTAssertNil(query[kSecValueData as String] as? Data) + XCTAssertNil(query[kSecAttrAccessGroup as String] as? String) + XCTAssertNil(query[kSecAttrSynchronizable as String] as? Bool) + #if canImport(LocalAuthentication) && !os(tvOS) + XCTAssertNil(query[kSecUseAuthenticationContext as String] as? LAContext) + #endif + } + + func testBaseQueryIncludesAdditionalAttributes() { + let key = "foo" + let value = "bar" + sut = SimpleKeychain(attributes: [key: value]) + let query = sut.baseQuery() + XCTAssertEqual(query[key] as? String, value) + } + + func testBaseQuerySupersedesAdditionalAttributes() { + let key = kSecAttrService as String + let value = "foo" + sut = SimpleKeychain(attributes: [key: value]) + let query = sut.baseQuery() + XCTAssertEqual(query[key] as? String, sut.service) + } + + func testBaseQueryIncludesAccountAttribute() { + let key = "foo" + let query = sut.baseQuery(withKey: key) + XCTAssertEqual(query[kSecAttrAccount as String] as? String, key) + } + + func testBaseQueryIncludesDataAttribute() { + let data = Data() + let query = sut.baseQuery(data: data) + XCTAssertEqual(query[kSecValueData as String] as? Data, data) + } + + func testBaseQueryIncludesAccessGroupAttribute() { + sut = SimpleKeychain(accessGroup: "foo") + let query = sut.baseQuery() + XCTAssertEqual(query[kSecAttrAccessGroup as String] as? String, sut.accessGroup) + } + + func testBaseQueryIncludesSynchronizableAttribute() { + sut = SimpleKeychain(synchronizable: true) + let query = sut.baseQuery() + XCTAssertEqual(query[kSecAttrSynchronizable as String] as? Bool, sut.isSynchronizable) + } + + #if canImport(LocalAuthentication) && !os(tvOS) + func testBaseQueryIncludesContextAttribute() { + sut = SimpleKeychain(context: LAContext()) + let query = sut.baseQuery() + XCTAssertEqual(query[kSecUseAuthenticationContext as String] as? LAContext, sut.context) } + #endif + + func testGetAllQueryContainsBaseQuery() { + let baseQuery = sut.baseQuery() + let query = sut.getAllQuery + XCTAssertTrue(query.containsBaseQuery(baseQuery)) + } + + func testGetAllQueryContainsReturnAttribute() { + let query = sut.getAllQuery + XCTAssertEqual(query[kSecReturnAttributes as String] as? Bool, true) + } + + func testGetAllQueryContainsLimitAttribute() { + let query = sut.getAllQuery + XCTAssertEqual(query[kSecMatchLimit as String] as? String, kSecMatchLimitAll as String) + } + + func testGetOneQueryContainsBaseQuery() { + let key = "foo" + let baseQuery = sut.baseQuery(withKey: key) + let query = sut.getOneQuery(byKey: key) + XCTAssertTrue(query.containsBaseQuery(baseQuery)) + } + + func testGetOneQueryContainsDataAttribute() { + let query = sut.getOneQuery(byKey: "foo") + XCTAssertEqual(query[kSecReturnData as String] as? Bool, true) + } + + func testGetOneQueryContainsLimitAttribute() { + let query = sut.getOneQuery(byKey: "foo") + XCTAssertEqual(query[kSecMatchLimit as String] as? String, kSecMatchLimitOne as String) + } + + func testSetQueryContainsBaseQuery() { + let key = "foo" + let data = Data() + let baseQuery = sut.baseQuery(withKey: key, data: data) + let query = sut.setQuery(forKey: key, data: data) + XCTAssertTrue(query.containsBaseQuery(baseQuery)) + } + + func testSetQueryIncludesAccessControlAttribute() { + sut = SimpleKeychain(accessControlFlags: .userPresence) + let query = sut.setQuery(forKey: "foo", data: Data()) + XCTAssertNotNil(query[kSecAttrAccessControl as String]) + } + + #if os(macOS) + func testSetQueryDoesNotIncludeAccessibilityAttributeByDefault() { + let query = sut.setQuery(forKey: "foo", data: Data()) + XCTAssertNil(query[kSecAttrAccessible as String] as? String) + } + + func testSetQueryIncludesAccessibilityAttributeWhenICloudSharingIsEnabled() { + sut = SimpleKeychain(synchronizable: true) + let query = sut.setQuery(forKey: "foo", data: Data()) + let expectedAccessibility = sut.accessibility.rawValue as String + XCTAssertEqual(query[kSecAttrAccessible as String] as? String, expectedAccessibility) + } + + func testSetQueryIncludesAccessibilityAttributeWhenDataProtectionIsEnabled() { + let attributes = [kSecUseDataProtectionKeychain as String: kCFBooleanTrue as Any] + sut = SimpleKeychain(attributes: attributes) + let query = sut.setQuery(forKey: "foo", data: Data()) + let expectedAccessibility = sut.accessibility.rawValue as String + XCTAssertEqual(query[kSecAttrAccessible as String] as? String, expectedAccessibility) + } + #else + func testSetQueryIncludesAccessibilityAttribute() { + let query = sut.setQuery(forKey: "foo", data: Data()) + let expectedAccessibility = sut.accessibility.rawValue as String + XCTAssertEqual(query[kSecAttrAccessible as String] as? String, expectedAccessibility) + } + #endif } -public func containBaseQuery(_ baseQuery: [String: Any]) -> Nimble.Predicate<[String: Any]> { - return Predicate<[String: Any]>.define("contains base query <\(baseQuery)>") { expression, failureMessage in - guard let actual = try expression.evaluate() else { - return PredicateResult(status: .doesNotMatch, message: failureMessage) - } - let filtered = actual.filter { element in +public extension Dictionary where Key == String, Value == Any { + func containsBaseQuery(_ baseQuery: [String: Any]) -> Bool { + let filtered = self.filter { element in return baseQuery.keys.contains(element.key) } - return PredicateResult(bool: filtered == baseQuery, message: failureMessage) + return filtered == baseQuery } } public func ==(lhs: [String: Any], rhs: [String: Any]) -> Bool { return NSDictionary(dictionary: lhs).isEqual(to: rhs) } +