Skip to content

Commit

Permalink
Merge pull request #1 from shakurocom/Keychain-v1.0.0
Browse files Browse the repository at this point in the history
Keychain v1.0.0
  • Loading branch information
sergeyyypopov authored Aug 29, 2022
2 parents f5de672 + 78abe6a commit 8c68a70
Show file tree
Hide file tree
Showing 29 changed files with 2,299 additions and 0 deletions.
96 changes: 96 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
swiftlint_version: 0.43.1

excluded: # paths to ignore during linting. Takes precedence over `included`.
- Pods

analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
- unused_declaration
- unused_import

deployment_target:
iOS_deployment_target: 10.0

closure_body_length:
warning: 30
error: 30

identifier_name:
max_length: 150

file_length:
warning: 1500
error: 1500
#ignore_comment_only_lines: true

function_body_length:
warning: 200
error: 200

function_parameter_count:
warning: 8
error: 8

line_length:
warning: 1000
error: 1000

type_body_length:
warning: 1000
error: 1000

type_name:
max_length: 150

cyclomatic_complexity:
warning: 30
error: 30

opt_in_rules:
- closure_body_length
# - closure_end_indentation # disabled, because xcode's block indentation depends on `[weak self]`
- closure_spacing
- contains_over_filter_count
- contains_over_filter_is_empty
- discouraged_optional_boolean
- duplicate_imports
- empty_collection_literal
- empty_count
- empty_string
- explicit_init
- fatal_error_message
# - file_name # disabled, because results are not consistent
# - file_header
- first_where
- force_unwrapping
- identical_operands
# - indentation_width # disabled, because multiline arguments are not recognized properly
- inert_defer
- last_where
- legacy_random
- let_var_whitespace
# - multiline_arguments
- multiline_parameters
- multiple_closures_with_trailing_closure
- no_space_in_method_call
# - object_literal
- operator_usage_whitespace
- optional_enum_case_matching
- prefer_self_type_over_type_of_self
- prefixed_toplevel_constant
- private_action
- private_over_fileprivate
- prohibited_super_call
- reduce_into
- redundant_nil_coalescing
- single_test_class
- sorted_first_last
# - sorted_imports
- strict_fileprivate
- strong_iboutlet
- toggle_bool
# - type_contents_order # https://realm.github.io/SwiftLint/type_contents_order.html
# - trailing_closure
- unowned_variable_capture
- unused_setter_value
- vertical_parameter_alignment_on_call
- yoda_condition
13 changes: 13 additions & 0 deletions Keychain.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Keychain.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
204 changes: 204 additions & 0 deletions KeychainWrapperTests/KeychainWrapperTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//
// Copyright (c) 2017 Shakuro (https://shakuro.com/)
// Sergey Laschuk
//

@testable import Keychain_Framework
import XCTest

class KeychainWrapperTests: XCTestCase {

private struct KeychainData: Codable {
let value1: String
let value2: String
}

private enum Constant {
static let serviceName: String = "com.shakuro.keychainWrapperTest"
static let accountName: String = "TestingValues"
static let accountName2: String = "TestingValues2"
static let itemName: String = ""
static let invalidServiceName: String = serviceName + ".invalid"
static let etalonData: KeychainData = KeychainData(value1: "111", value2: "222")
static let etalonData2: KeychainData = KeychainData(value1: "1112", value2: "2223")
}

override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
try? KeychainWrapper.removeKeychainItems(serviceName: Constant.serviceName)
}

func testReadRemoveCollection() {
// 1) read - there should be no items in keychain
do {
let emptyItemsInfo: [KeychainWrapper.ItemInfo] = try KeychainWrapper.itemsInfo(serviceName: Constant.serviceName)
XCTAssertTrue(emptyItemsInfo.isEmpty)
let emptyItems: [KeychainWrapper.Item<KeychainData>] = try KeychainWrapper.keychainItems(serviceName: Constant.serviceName)
XCTAssertTrue(emptyItems.isEmpty)
} catch let error {
XCTFail("\(error)")
}

// 2) save etalon data
do {
let keychainItem = KeychainWrapper.Item(serviceName: Constant.serviceName,
account: Constant.accountName,
itemName: Constant.itemName,
accessGroup: nil,
secValue: Constant.etalonData)
try KeychainWrapper.saveKeychainItem(keychainItem)
} catch let error {
XCTFail("\(error)")
}
do {
let keychainItem = KeychainWrapper.Item(serviceName: Constant.serviceName,
account: Constant.accountName2,
itemName: Constant.itemName,
accessGroup: nil,
secValue: Constant.etalonData2)
try KeychainWrapper.saveKeychainItem(keychainItem)
} catch let error {
XCTFail("\(error)")
}

// 3) read data back
do {
let itemsInfo: [KeychainWrapper.ItemInfo] = try KeychainWrapper.itemsInfo(serviceName: Constant.serviceName)
XCTAssertTrue(itemsInfo.count == 2)
let items: [KeychainWrapper.Item<KeychainData>] = try KeychainWrapper.keychainItems(serviceName: Constant.serviceName)
XCTAssertTrue(items.count == itemsInfo.count)
let itemInfoMap: [String: KeychainWrapper.ItemInfo] = itemsInfo.reduce(into: [:], { (res, info) in
res[info.account] = info
})
XCTAssertTrue(itemInfoMap.count == itemsInfo.count)
let itemMap: [String: KeychainWrapper.Item<KeychainData>] = items.reduce(into: [:], { (res, info) in
res[info.account] = info
})
XCTAssertTrue(itemMap.count == items.count)

let accInfo1 = try assertNotNilAndUnwrap(itemInfoMap[Constant.accountName])
let acc1 = try assertNotNilAndUnwrap(itemMap[Constant.accountName])
XCTAssertEqual(acc1.secValue.value1, Constant.etalonData.value1)
XCTAssertEqual(acc1.secValue.value2, Constant.etalonData.value2)
try [accInfo1, acc1.info].forEach { (info) in
XCTAssertEqual(info.serviceName, Constant.serviceName)
XCTAssertEqual(info.account, Constant.accountName)
let itemNameNotNil = try assertNotNilAndUnwrap(info.itemName)
XCTAssertEqual(itemNameNotNil, Constant.itemName)
}

let accInfo2 = try assertNotNilAndUnwrap(itemInfoMap[Constant.accountName2])
let acc2 = try assertNotNilAndUnwrap(itemMap[Constant.accountName2])
XCTAssertEqual(acc2.secValue.value1, Constant.etalonData2.value1)
XCTAssertEqual(acc2.secValue.value2, Constant.etalonData2.value2)
try [accInfo2, acc2.info].forEach { (info) in
XCTAssertEqual(info.serviceName, Constant.serviceName)
XCTAssertEqual(info.account, Constant.accountName2)
let itemNameNotNil = try assertNotNilAndUnwrap(info.itemName)
XCTAssertEqual(itemNameNotNil, Constant.itemName)
}
} catch let error {
XCTFail("\(error)")
}

// 6) delete items
do {
try KeychainWrapper.removeKeychainItems(serviceName: Constant.serviceName)
} catch let error {
XCTFail("\(error)")
}

// 7) read after deletion
do {
let emptyItemsInfo: [KeychainWrapper.ItemInfo] = try KeychainWrapper.itemsInfo(serviceName: Constant.serviceName)
XCTAssertTrue(emptyItemsInfo.isEmpty)
} catch let error {
XCTFail("\(error)")
}
}

func testReadWriteRemove() {
// 1) read - there should be no items in keychain
do {
let nilItem: KeychainWrapper.Item<KeychainData>? = try KeychainWrapper.keychainItem(serviceName: Constant.serviceName, account: Constant.accountName)
XCTAssertNil(nilItem, "unexpected item found in keychain")
} catch let error {
XCTFail("\(error)")
}
do {
let nilItem: KeychainWrapper.Item<KeychainData>? = try KeychainWrapper.keychainItem(serviceName: Constant.invalidServiceName, account: Constant.accountName)
XCTAssertNil(nilItem, "unexpected item found in keychain")
} catch let error {
XCTFail("\(error)")
}

// 2) save etalon data
do {
let keychainItem = KeychainWrapper.Item(serviceName: Constant.serviceName, account: Constant.accountName, itemName: Constant.itemName, accessGroup: nil, secValue: Constant.etalonData)
try KeychainWrapper.saveKeychainItem(keychainItem)
} catch let error {
XCTFail("\(error)")
}

// 3) read data back
do {
let keychainItem: KeychainWrapper.Item<KeychainData>? = try KeychainWrapper.keychainItem(serviceName: Constant.serviceName, account: Constant.accountName)
let keychainItemNotNil = try assertNotNilAndUnwrap(keychainItem)
XCTAssertEqual(keychainItemNotNil.serviceName, Constant.serviceName)
XCTAssertEqual(keychainItemNotNil.account, Constant.accountName)
let itemNameNotNil = try assertNotNilAndUnwrap(keychainItemNotNil.itemName)
XCTAssertEqual(itemNameNotNil, Constant.itemName)
XCTAssertEqual(keychainItemNotNil.secValue.value1, Constant.etalonData.value1)
XCTAssertEqual(keychainItemNotNil.secValue.value2, Constant.etalonData.value2)
} catch let error {
XCTFail("\(error)")
}

// 4) read data with invalid service name
do {
let keychainItem: KeychainWrapper.Item<KeychainData>? = try KeychainWrapper.keychainItem(serviceName: Constant.invalidServiceName, account: Constant.accountName)
XCTAssertNil(keychainItem)
} catch let error {
XCTFail("\(error)")
}

// 5) read data of invalid type
do {
let keychainItem: KeychainWrapper.Item<String>? = try KeychainWrapper.keychainItem(serviceName: Constant.serviceName, account: Constant.accountName)
XCTFail("there should be an error in previous line. Returned result: \(String(describing: keychainItem))")
} catch let error as KeychainWrapper.Error {
switch error {
case .unexpectedKeychainItemData:
// this is the expected result
break

default:
XCTFail("\(error)")
}
} catch let error {
XCTFail("\(error)")
}

// 6) delete item
do {
try KeychainWrapper.removeKeychainItem(serviceName: Constant.serviceName, account: Constant.accountName)
} catch let error {
XCTFail("\(error)")
}

// 7) read after deletion
do {
let nilItem: KeychainWrapper.Item<KeychainData>? = try KeychainWrapper.keychainItem(serviceName: Constant.serviceName, account: Constant.accountName)
XCTAssertNil(nilItem)
} catch let error {
XCTFail("\(error)")
}
}

}
15 changes: 15 additions & 0 deletions KeychainWrapperTests/UnexpectedNilError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
//
//

import XCTest

struct UnexpectedNilError: Error {}

public func assertNotNilAndUnwrap<T>(_ variable: T?, message: String = "Unexpected nil variable", file: StaticString = #file, line: UInt = #line) throws -> T {
guard let variable = variable else {
XCTFail(message, file: file, line: line)
throw UnexpectedNilError()
}
return variable
}
Loading

0 comments on commit 8c68a70

Please sign in to comment.