Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 130 additions & 2 deletions Common/Sources/DataSources/Keychain/JWTTokenStorageable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by 강동영 on 1/13/26.
//

import Foundation

public protocol JWTTokenStorageable: Sendable {
func save(accessToken: String, refreshToken: String?) throws
Expand All @@ -13,10 +14,10 @@ public protocol JWTTokenStorageable: Sendable {
func exists() throws -> Bool
}

public final class JWTokenStorage: KeychainTokenStorage, JWTTokenStorageable {
public final class JWTokenStorage: /*KeychainTokenStorage,*/ JWTTokenStorageable {
private let service: String

public override init(service: String = HambugKeychainKey.serviceID) {
public init(service: String = HambugKeychainKey.serviceID) {
self.service = service
}

Expand Down Expand Up @@ -44,3 +45,130 @@ public final class JWTokenStorage: KeychainTokenStorage, JWTTokenStorageable {
try delete(.refreshToken)
}
}

extension JWTokenStorage {

func set(_ value: String, for key: HambugKeychainKey) throws {
let data = value.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key.toString,
kSecValueData as String: data
]

let status = SecItemAdd(query as CFDictionary, nil)

do {
try handleError(status)
} catch KeychainError.duplicateItem {
// 중복 된 아이템이 있다면 업데이트 수행
try updateExisting(data, for: key)
}
}

func updateExisting(_ data: Data, for key: HambugKeychainKey) throws {
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key.toString
]

let attributes: [String: Any] = [
kSecValueData as String: data
]

let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributes as CFDictionary)

try handleError(updateStatus)
}

func string(for key: HambugKeychainKey) throws -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key.toString,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]

var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)

try handleError(status)

guard let data = item as? Data,
let string = String(data: data, encoding: .utf8)
else {
throw KeychainError.unexpectedPasswordData
}
return string
}

func delete(_ key: HambugKeychainKey) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key.toString
]

let status = SecItemDelete(query as CFDictionary)

try handleError(status)
}

func contains(_ key: HambugKeychainKey) throws -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: key.toString,
]

return SecItemCopyMatching(query as CFDictionary, nil) == errSecSuccess
}

func handleError(_ status: OSStatus) throws {
switch status {
case errSecSuccess:
return
case errSecDuplicateItem:
throw KeychainError.duplicateItem
case errSecItemNotFound:
throw KeychainError.itemNotFound
default:
throw KeychainError.unexpected(status)
}
}
}

//public final class JWTokenStorage: KeychainTokenStorage, JWTTokenStorageable {
// private let service: String
//
// public override init(service: String = HambugKeychainKey.serviceID) {
// self.service = service
// }
//
// public func save(accessToken: String, refreshToken: String?) throws {
// try set(accessToken, for: .accessToken)
//
// if let refresh = refreshToken {
// try set(refresh, for: .refreshToken)
// }
// }
//
// public func load() -> (accessToken: String?, refreshToken: String?) {
// let access = try? string(for: .accessToken)
// let refresh = try? string(for: .refreshToken)
//
// return (access, refresh)
// }
//
// public func exists() throws -> Bool {
// try contains(.accessToken)
// }
//
// public func clear() throws {
// try delete(.accessToken)
// try delete(.refreshToken)
// }
//}
74 changes: 74 additions & 0 deletions Hambug.xcodeproj/xcshareddata/xcschemes/HambugUITests.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "915BC5E92E3CB9B80062B78E"
BuildableName = "HambugUITests.xctest"
BlueprintName = "HambugUITests"
ReferencedContainer = "container:Hambug.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "915BC5D12E3CB9B50062B78E"
BuildableName = "Hambug.app"
BlueprintName = "Hambug"
ReferencedContainer = "container:Hambug.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "915BC5D12E3CB9B50062B78E"
BuildableName = "Hambug.app"
BlueprintName = "Hambug"
ReferencedContainer = "container:Hambug.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
70 changes: 70 additions & 0 deletions HambugUITests/Core/Base/TestUIBase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// TestUIBase.swift
// Hambug
//
// Created by 강동영 on 2/7/26.
//

import XCTest

class TestUIBase: XCTestCase {
static var launched = false

var app: XCUIApplication?

convenience init(app: XCUIApplication?) {
self.init()
self.app = app
}

override func setUp() {
super.setUp()
// 앱 Launch 여부(시간 단축용)
if TestUIBase.launched == false {
initApp(withLaunch: true)
TestUIBase.launched = true
} else {
initApp(withLaunch: false)
}
}

private func initApp(withLaunch: Bool) {
guard app == nil else { return }
let application = XCUIApplication()
if withLaunch {
application.launchArguments.append("UITest")
application.launch()
}
app = application
}

override func tearDown() {
super.tearDown()
if let cnt = testRun?.failureCount, cnt > 0 {
// 한번이라도 실패시 다시 Launch토록 설정
TestUIBase.launched = false
}
}

enum DelayType: TimeInterval {
case short = 0.7
case medium = 1.3
case long = 1.6
}

/// 딜레이 걸기
func delay(_ timeout: TimeInterval) {
wait(for: [.delay], timeout: timeout)
}
func delay(_ type: DelayType) {
wait(for: [.delay], timeout: type.rawValue)
}

/// 스크린샷
func takeScreenshot(name: String) {
let fullScreenshot = XCUIScreen.main.screenshot()
let screenshot = XCTAttachment(uniformTypeIdentifier: "public.png", name: "Screenshot-\(name)-\(UIDevice.current.name).png", payload: fullScreenshot.pngRepresentation, userInfo: nil)
screenshot.lifetime = .keepAlways
add(screenshot)
}
}
16 changes: 16 additions & 0 deletions HambugUITests/Core/Extensions/XCTestExpectation+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// XCTestExpectation+.swift
// Hambug
//
// Created by 강동영 on 2/7/26.
//

import XCTest

extension XCTestExpectation {
static var delay: XCTestExpectation {
let delay = XCTestExpectation()
delay.isInverted = true
return delay
}
}
Loading