From 8b103b43422c3cd837d3b6f2fdcfcdc0fed6cec0 Mon Sep 17 00:00:00 2001 From: Roy Date: Mon, 26 Oct 2026 15:48:37 +0900 Subject: [PATCH] =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20API=20=EA=B3=84?= =?UTF-8?q?=EC=95=BD=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EB=AA=A8=EB=93=88=20=EA=B5=AC=EC=A1=B0=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이동된 APIEndpoint와 공통 유틸 모듈을 테스트에서 직접 가져옵니다. 통합된 클래스 Repository와 로컬 AI UseCase 이름으로 테스트 참조를 갱신합니다. --- .../Sources/ClassAIChatDependencyTests.swift | 4 +-- .../ClassAIFeedbackDependencyTests.swift | 26 ++++++++++++++--- ...ClassAIFeedbackFoundationModelsTests.swift | 8 +++--- .../ClassAIFoundationModelsTests.swift | 28 +++++++++---------- .../Sources/ClassBattleAPIContractTests.swift | 1 + .../Sources/ClassDebateAPIContractTests.swift | 1 + .../Tests/Sources/ClassMemberAPITests.swift | 1 + .../Sources/ClassRecommendationTests.swift | 13 ++++----- .../Sources/ClassResultAPIContractTests.swift | 14 ++++++---- .../Tests/Sources/ClassTicketAPITests.swift | 3 +- .../Sources/ClassTopicAPIContractTests.swift | 1 + 11 files changed, 62 insertions(+), 38 deletions(-) diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIChatDependencyTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIChatDependencyTests.swift index bc7b1379..b71fd74d 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIChatDependencyTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIChatDependencyTests.swift @@ -16,8 +16,8 @@ struct ClassAIChatDependencyTests { let isLive = withDependencies { $0.context = .live } operation: { - @Dependency(\.classAIChatUseCase) var useCase - return useCase is ClassAIChatUseCaseImpl + @Dependency(\.classLocalAIUseCase) var useCase + return useCase is ClassUseCaseImpl } #expect(isLive) diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackDependencyTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackDependencyTests.swift index 675a2e67..8e5efa8c 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackDependencyTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackDependencyTests.swift @@ -23,17 +23,17 @@ struct ClassAIFeedbackDependencyTests { report: MockClassReport.figma, isOwner: false ) - let mock = MockClassAIFeedbackUseCase { received in + let mock = FeedbackUseCaseStub { received in #expect(received.roomID == context.roomID) return .classInsight("generated") } let useCase = withDependencies { - $0.classAIFeedbackUseCase = mock + $0.classLocalAIUseCase = mock } operation: { - @Dependency(\.classAIFeedbackUseCase) var useCase + @Dependency(\.classLocalAIUseCase) var useCase return useCase } - let result = try await useCase.generate(context: context) + let result = try await useCase.generateAIFeedback(context: context) #expect(result == .classInsight("generated")) #expect(ClassAIFeedbackError.deviceNotEligible.isUnavailable) #expect(ClassAIFeedbackError.appleIntelligenceNotEnabled.isUnavailable) @@ -130,3 +130,21 @@ struct ClassAIFeedbackDependencyTests { } #endif } + +private struct FeedbackUseCaseStub: ClassLocalAIInterface { + let generate: @Sendable (ClassAIFeedbackContext) async throws -> ClassAIFeedbackResult + + func startAIChat(_: UUID, _: ClassAIChatContext) async throws { + throw ClassAIFeedbackError.modelNotReady + } + + func respondAIChat(_: UUID, _: String, isFinalRound _: Bool) async throws -> String { + throw ClassAIFeedbackError.modelNotReady + } + + func endAIChat(_: UUID) async {} + + func generateAIFeedback(context: ClassAIFeedbackContext) async throws -> ClassAIFeedbackResult { + try await generate(context) + } +} diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackFoundationModelsTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackFoundationModelsTests.swift index c9ffe9e7..3f2a768c 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackFoundationModelsTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFeedbackFoundationModelsTests.swift @@ -66,7 +66,7 @@ final class ClassAIFeedbackFoundationModelsTests: XCTestCase { layout: .summary ) let context = try ClassAIFeedbackContext.personal(room: room, report: report, isOwner: false) - let client = ClassAIFeedbackUseCaseDependency.liveValue + let client = ClassLocalAIUseCaseDependency.liveValue let diagnostics = FeedbackAuditDiagnostics() defer { let attachment = XCTAttachment(string: diagnostics.messages.joined(separator: "\n")) @@ -80,15 +80,15 @@ final class ClassAIFeedbackFoundationModelsTests: XCTestCase { if #available(iOS 26.0, *) { diagnostics.append("diagnosticSink=installed") result = try await ClassAIFeedbackFoundationEngine.$auditDiagnostic.withValue({ diagnostics.append($0) }) { - try await client.generate(context: context) + try await client.generateAIFeedback(context: context) } } else { diagnostics.append("diagnosticSink=unavailable") - result = try await client.generate(context: context) + result = try await client.generateAIFeedback(context: context) } #else diagnostics.append("diagnosticSink=unavailable") - result = try await client.generate(context: context) + result = try await client.generateAIFeedback(context: context) #endif let attachment = XCTAttachment( diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFoundationModelsTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFoundationModelsTests.swift index c2832924..b1e5b2a5 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFoundationModelsTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassAIFoundationModelsTests.swift @@ -20,22 +20,22 @@ final class ClassAIFoundationModelsTests: XCTestCase { throw XCTSkip("실기 Foundation Models 감사는 PICKE_CLASS_FOUNDATION_AUDIT=1에서만 실행합니다.") } - let client = ClassAIChatUseCaseDependency.liveValue + let client = ClassLocalAIUseCaseDependency.liveValue let conversationID = UUID() do { - try await client.start(conversationID, ClassAIChatContext( + try await client.startAIChat(conversationID, ClassAIChatContext( question: "뒤샹의 변기는 예술일까?", selectedStance: "예술이 될 수 있다", opponentStance: "예술이 아니다", opponentPhilosopherName: "플라톤" )) - let reply = try await client.respond( + let reply = try await client.respondAIChat( conversationID, "평범한 물건도 새로운 의미를 부여하면 예술이 될 수 있어요.", isFinalRound: false ) - await client.end(conversationID) + await client.endAIChat(conversationID) let attachment = XCTAttachment(string: reply) attachment.name = "Class AI 실제 모델 응답" @@ -48,7 +48,7 @@ final class ClassAIFoundationModelsTests: XCTestCase { "모델 응답에 한글 음절이 포함되어야 합니다." ) } catch { - await client.end(conversationID) + await client.endAIChat(conversationID) if let modelError = error as? ClassAIChatError, modelError == .unavailable || modelError == .unsupportedLanguage || modelError == .deviceNotEligible || modelError == .appleIntelligenceNotEnabled @@ -83,14 +83,14 @@ final class ClassAIFoundationModelsTests: XCTestCase { "진짜? 왜 그렇게 생각해요?", "형식뿐 아니라 보는 사람의 해석도 중요하다는 점을 정리하고 싶어요.", ] - let client = ClassAIChatUseCaseDependency.liveValue + let client = ClassLocalAIUseCaseDependency.liveValue var replies: [String] = [] do { - try await client.start(id, context) - let first = try await client.respond(id, prompts[0], isFinalRound: false) - let second = try await client.respond(id, prompts[1], isFinalRound: false) + try await client.startAIChat(id, context) + let first = try await client.respondAIChat(id, prompts[0], isFinalRound: false) + let second = try await client.respondAIChat(id, prompts[1], isFinalRound: false) replies = [first, second] - await client.end(id) + await client.endAIChat(id) let prior: [ClassAIChatMessage] = [ .init(speaker: .user, text: prompts[0]), @@ -105,10 +105,10 @@ final class ClassAIFoundationModelsTests: XCTestCase { opponentPhilosopherName: nil, priorMessages: prior ) - try await client.start(id, resumed) - let third = try await client.respond(id, prompts[2], isFinalRound: true) + try await client.startAIChat(id, resumed) + let third = try await client.respondAIChat(id, prompts[2], isFinalRound: true) replies.append(third) - await client.end(id) + await client.endAIChat(id) let limit = if #available(iOS 26.4, *) { 1440 @@ -142,7 +142,7 @@ final class ClassAIFoundationModelsTests: XCTestCase { XCTAssertTrue(third.contains("해석"), "마지막 사용자 근거를 반영해야 합니다.") XCTAssertTrue(third.contains("형식") || third.contains("기술"), "반대 입장의 근거를 반영해야 합니다.") } catch { - await client.end(id) + await client.endAIChat(id) throw error } } diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassBattleAPIContractTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassBattleAPIContractTests.swift index b885a0db..16779be3 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassBattleAPIContractTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassBattleAPIContractTests.swift @@ -1,4 +1,5 @@ @testable import ClassDomain +import APIEndpoint import ClassDomainInterface import Foundation import PickeNetwork diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassDebateAPIContractTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassDebateAPIContractTests.swift index 9f43ce8d..aa4c6edc 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassDebateAPIContractTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassDebateAPIContractTests.swift @@ -1,4 +1,5 @@ @testable import ClassDomain +import APIEndpoint import ClassDomainInterface import Foundation import PickeNetwork diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassMemberAPITests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassMemberAPITests.swift index 0a8cf9a0..87d61e13 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassMemberAPITests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassMemberAPITests.swift @@ -1,4 +1,5 @@ @testable import ClassDomain +import APIEndpoint import ClassDomainInterface import Foundation import PickeNetwork diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassRecommendationTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassRecommendationTests.swift index 088860e4..b9b4a243 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassRecommendationTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassRecommendationTests.swift @@ -1,4 +1,5 @@ @testable import ClassDomain +import APIEndpoint import ClassDomainInterface import Dependencies import Foundation @@ -67,13 +68,11 @@ struct ClassRecommendationTests { @Test func 라이브_클래스_목록은_목_저장소가_아닌_서버를_쓴다() async throws { let json = #"{"statusCode":200,"data":[]}"# - let live = ClassLiveRepository( - api: withDependencies { - $0.networkClient = ClassRecommendationStubClient(data: Data(json.utf8)) - } operation: { - ClassRepositoryImpl() - } - ) + let live = withDependencies { + $0.networkClient = ClassRecommendationStubClient(data: Data(json.utf8)) + } operation: { + ClassRepositoryImpl() + } #expect(try await live.fetchMyClasses().isEmpty) } diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassResultAPIContractTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassResultAPIContractTests.swift index ce971e2d..53e31e9c 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassResultAPIContractTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassResultAPIContractTests.swift @@ -1,22 +1,24 @@ @testable import ClassDomain +import APIEndpoint import ClassDomainInterface import Foundation +import PickeCoreUtility import PickeNetwork import Testing struct ClassResultAPIContractTests { @Test func 결과_열람_만료와_권한_오류를_구분한다() { - #expect(ClassResultRepositoryImpl.resultError(code: "CLASS_409_RESULT_EXPIRED", message: nil) == .resultExpired) - #expect(ClassResultRepositoryImpl.resultError(code: "CLASS_403_MEMBER", message: nil) == .accessDenied) - #expect(ClassResultRepositoryImpl.feedbackError(code: "CLASS_409_RESULT_EXPIRED") == .resultExpired) - #expect(ClassResultRepositoryImpl.feedbackError(code: "CLASS_403_OWNER") == .accessDenied) - #expect(ClassResultRepositoryImpl.feedbackError(code: "CLASS_409_REPORT_RETRY") == .generationFailed) + #expect(ClassRepositoryImpl.resultError(code: "CLASS_409_RESULT_EXPIRED", message: nil) == .resultExpired) + #expect(ClassRepositoryImpl.resultError(code: "CLASS_403_MEMBER", message: nil) == .accessDenied) + #expect(ClassRepositoryImpl.feedbackError(code: "CLASS_409_RESULT_EXPIRED") == .resultExpired) + #expect(ClassRepositoryImpl.feedbackError(code: "CLASS_403_OWNER") == .accessDenied) + #expect(ClassRepositoryImpl.feedbackError(code: "CLASS_409_REPORT_RETRY") == .generationFailed) } @Test func 감싼_네트워크_디코딩_오류는_재시도_대상에서_제외한다() { - let error = ClassResultRepositoryImpl.feedbackError(PickeNetworkError.decoding(.dataMissing)) + let error = ClassRepositoryImpl.feedbackError(PickeNetworkError.decoding(.dataMissing)) #expect(error == .invalidResponse) #expect(!error.isRetryable) } diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassTicketAPITests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassTicketAPITests.swift index 1137162a..b466520e 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassTicketAPITests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassTicketAPITests.swift @@ -1,4 +1,5 @@ @testable import ClassDomain +import APIEndpoint import ClassDomainInterface import Foundation import PickeCoreUtility @@ -22,7 +23,7 @@ struct ClassTicketAPITests { let error = PickeNetworkError.response( ResponseError(httpStatus: status, code: code) ) - #expect(ClassTicketRepositoryImpl.map(error) == expected) + #expect(ClassRepositoryImpl.ticketError(error) == expected) } } diff --git a/Projects/Domain/ClassDomain/Tests/Sources/ClassTopicAPIContractTests.swift b/Projects/Domain/ClassDomain/Tests/Sources/ClassTopicAPIContractTests.swift index 4dd4f66c..949c179a 100644 --- a/Projects/Domain/ClassDomain/Tests/Sources/ClassTopicAPIContractTests.swift +++ b/Projects/Domain/ClassDomain/Tests/Sources/ClassTopicAPIContractTests.swift @@ -1,5 +1,6 @@ @testable import ClassDomain import ClassDomainInterface +import APIEndpoint import Foundation import PickeNetwork import Testing