diff --git a/Projects/Domain/README.md b/Projects/Domain/README.md index 2123bb9..47d95f2 100644 --- a/Projects/Domain/README.md +++ b/Projects/Domain/README.md @@ -5,6 +5,7 @@ ## 현재 상태 - Auth 포트 추가 (`AuthSession`, `AuthError`, `AuthProvider`, `AuthClient`) +- User 포트 추가 (`UserProfile`, `OnboardingDraft`, `Gender`, `Interest`, `UserError`, `UserClient`) ## 이후 패턴 - `Domain//{Model,Client,Error}` @@ -20,14 +21,22 @@ - UseCase 층 없음 ## 주요 진입점 -- `Sources/Auth/AuthClient.swift` -- `Sources/Auth/AuthSession.swift` -- `Sources/Auth/AuthError.swift` -- `Sources/Auth/AuthProvider.swift` +- `Sources/Auth/Client/AuthClient.swift` +- `Sources/Auth/Model/AuthSession.swift` +- `Sources/Auth/Model/AuthProvider.swift` +- `Sources/Auth/Error/AuthError.swift` +- `Sources/User/Client/UserClient.swift` +- `Sources/User/Model/UserProfile.swift` +- `Sources/User/Model/OnboardingDraft.swift` +- `Sources/User/Model/Gender.swift` +- `Sources/User/Model/Interest.swift` +- `Sources/User/Error/UserError.swift` ## 테스트 포인트 - 세션 동등성/Codable 왕복 - `AuthClient.testValue` 기본 unimplemented 동작 (`@DependencyClient`) +- User model 동등성/Codable 왕복 +- `UserClient.testValue` 기본 unimplemented 동작 (`@DependencyClient`) ## 관련 문서 - [ARCHITECTURE.md](../../docs/ARCHITECTURE.md) diff --git a/Projects/Domain/Sources/Auth/AuthClient.swift b/Projects/Domain/Sources/Auth/Client/AuthClient.swift similarity index 100% rename from Projects/Domain/Sources/Auth/AuthClient.swift rename to Projects/Domain/Sources/Auth/Client/AuthClient.swift diff --git a/Projects/Domain/Sources/Auth/AuthError.swift b/Projects/Domain/Sources/Auth/Error/AuthError.swift similarity index 100% rename from Projects/Domain/Sources/Auth/AuthError.swift rename to Projects/Domain/Sources/Auth/Error/AuthError.swift diff --git a/Projects/Domain/Sources/Auth/AuthProvider.swift b/Projects/Domain/Sources/Auth/Model/AuthProvider.swift similarity index 100% rename from Projects/Domain/Sources/Auth/AuthProvider.swift rename to Projects/Domain/Sources/Auth/Model/AuthProvider.swift diff --git a/Projects/Domain/Sources/Auth/AuthSession.swift b/Projects/Domain/Sources/Auth/Model/AuthSession.swift similarity index 100% rename from Projects/Domain/Sources/Auth/AuthSession.swift rename to Projects/Domain/Sources/Auth/Model/AuthSession.swift diff --git a/Projects/Domain/Sources/User/Client/UserClient.swift b/Projects/Domain/Sources/User/Client/UserClient.swift new file mode 100644 index 0000000..b063ff2 --- /dev/null +++ b/Projects/Domain/Sources/User/Client/UserClient.swift @@ -0,0 +1,18 @@ +import Foundation +import ThirdParty + +@DependencyClient +public struct UserClient: Sendable { + public var completeOnboarding: @Sendable (OnboardingDraft) async throws -> AuthSession +} + +extension UserClient: TestDependencyKey { + public static let testValue = UserClient() +} + +public extension DependencyValues { + var userClient: UserClient { + get { self[UserClient.self] } + set { self[UserClient.self] = newValue } + } +} diff --git a/Projects/Domain/Sources/User/Error/UserError.swift b/Projects/Domain/Sources/User/Error/UserError.swift new file mode 100644 index 0000000..354d473 --- /dev/null +++ b/Projects/Domain/Sources/User/Error/UserError.swift @@ -0,0 +1,10 @@ +import Foundation + +public enum UserError: Error, Equatable, Sendable { + case network + case unauthorized + case validation(message: String) + case updateFailed + case storage(message: String) + case unknown(message: String) +} diff --git a/Projects/Domain/Sources/User/Model/Gender.swift b/Projects/Domain/Sources/User/Model/Gender.swift new file mode 100644 index 0000000..32288ec --- /dev/null +++ b/Projects/Domain/Sources/User/Model/Gender.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum Gender: String, Equatable, Hashable, Sendable, Codable { + case male + case female + case other +} diff --git a/Projects/Domain/Sources/User/Model/Interest.swift b/Projects/Domain/Sources/User/Model/Interest.swift new file mode 100644 index 0000000..cd9bc3d --- /dev/null +++ b/Projects/Domain/Sources/User/Model/Interest.swift @@ -0,0 +1,11 @@ +import Foundation + +public struct Interest: Equatable, Hashable, Sendable, Codable { + public var id: String + public var name: String + + public init(id: String, name: String) { + self.id = id + self.name = name + } +} diff --git a/Projects/Domain/Sources/User/Model/OnboardingDraft.swift b/Projects/Domain/Sources/User/Model/OnboardingDraft.swift new file mode 100644 index 0000000..064a847 --- /dev/null +++ b/Projects/Domain/Sources/User/Model/OnboardingDraft.swift @@ -0,0 +1,20 @@ +import Foundation + +public struct OnboardingDraft: Equatable, Sendable { + public var nickname: String + public var gender: Gender + public var birthDate: Date + public var interestIDs: [String] + + public init( + nickname: String, + gender: Gender, + birthDate: Date, + interestIDs: [String] + ) { + self.nickname = nickname + self.gender = gender + self.birthDate = birthDate + self.interestIDs = interestIDs + } +} diff --git a/Projects/Domain/Sources/User/Model/UserProfile.swift b/Projects/Domain/Sources/User/Model/UserProfile.swift new file mode 100644 index 0000000..29dbc7e --- /dev/null +++ b/Projects/Domain/Sources/User/Model/UserProfile.swift @@ -0,0 +1,20 @@ +import Foundation + +public struct UserProfile: Equatable, Sendable, Codable { + public var nickname: String + public var gender: Gender + public var birthDate: Date + public var interestIDs: [String] + + public init( + nickname: String, + gender: Gender, + birthDate: Date, + interestIDs: [String] + ) { + self.nickname = nickname + self.gender = gender + self.birthDate = birthDate + self.interestIDs = interestIDs + } +} diff --git a/Projects/Domain/Tests/User/UserClientTests.swift b/Projects/Domain/Tests/User/UserClientTests.swift new file mode 100644 index 0000000..5d58f8a --- /dev/null +++ b/Projects/Domain/Tests/User/UserClientTests.swift @@ -0,0 +1,11 @@ +import Domain +import XCTest + +final class UserClientTests: XCTestCase { + func test_UserClient_testValue는_빈_클라이언트로_생성() { + // @DependencyClient 는 testValue = UserClient() 를 기본 제공한다. + // 미구현 endpoint 호출 시 issue를 내므로, 생성 가능성만 검증한다. + _ = UserClient.testValue + XCTAssertTrue(true) + } +} diff --git a/Projects/Domain/Tests/User/UserModelTests.swift b/Projects/Domain/Tests/User/UserModelTests.swift new file mode 100644 index 0000000..353bb16 --- /dev/null +++ b/Projects/Domain/Tests/User/UserModelTests.swift @@ -0,0 +1,70 @@ +import Domain +import XCTest + +final class UserModelTests: XCTestCase { + func test_성별_rawValue와_동등성() { + XCTAssertEqual(Gender.male.rawValue, "male") + XCTAssertEqual(Gender.female.rawValue, "female") + XCTAssertEqual(Gender.other.rawValue, "other") + XCTAssertEqual(Gender.male, Gender.male) + } + + func test_관심사_동등성_비교() { + let a = Interest(id: "sports", name: "운동") + let b = Interest(id: "sports", name: "운동") + XCTAssertEqual(a, b) + } + + func test_관심사_codable_왕복() throws { + let original = Interest(id: "music", name: "음악") + let data = try JSONEncoder().encode(original) + let decoded = try JSONDecoder().decode(Interest.self, from: data) + XCTAssertEqual(decoded, original) + } + + func test_프로필_동등성_비교() { + let birthDate = Date(timeIntervalSince1970: 0) + let a = UserProfile( + nickname: "모지", + gender: .female, + birthDate: birthDate, + interestIDs: ["sports", "music"] + ) + let b = UserProfile( + nickname: "모지", + gender: .female, + birthDate: birthDate, + interestIDs: ["sports", "music"] + ) + XCTAssertEqual(a, b) + } + + func test_프로필_codable_왕복() throws { + let original = UserProfile( + nickname: "모지", + gender: .other, + birthDate: Date(timeIntervalSince1970: 1_000), + interestIDs: ["travel"] + ) + let data = try JSONEncoder().encode(original) + let decoded = try JSONDecoder().decode(UserProfile.self, from: data) + XCTAssertEqual(decoded, original) + } + + func test_온보딩초안_동등성_비교() { + let birthDate = Date(timeIntervalSince1970: 2_000) + let a = OnboardingDraft( + nickname: "모지", + gender: .male, + birthDate: birthDate, + interestIDs: ["food"] + ) + let b = OnboardingDraft( + nickname: "모지", + gender: .male, + birthDate: birthDate, + interestIDs: ["food"] + ) + XCTAssertEqual(a, b) + } +}