diff --git a/B.READ/B.READ.xcodeproj/project.pbxproj b/B.READ/B.READ.xcodeproj/project.pbxproj index 7e5a7b0b..9312dbd3 100644 --- a/B.READ/B.READ.xcodeproj/project.pbxproj +++ b/B.READ/B.READ.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ Sources/Domain/Repository/Inerface/RecordRepository.swift, Sources/Domain/Repository/Inerface/SummaryRepository.swift, Sources/Domain/Repository/Inerface/UserInfoRepository.swift, + "Sources/Util/Extensions/String+.swift", ); target = 16BE8C682DED50C0006FFD00 /* RepositoryTest */; }; @@ -159,6 +160,7 @@ Sources/Network/NetworkClient/RequestConvertible.swift, "Sources/Util/Extensions/Bundle+.swift", "Sources/Util/Extensions/Date+.swift", + "Sources/Util/Extensions/String+.swift", ); target = 16BE931F2DED713E006FFD00 /* ServiceTest */; }; @@ -232,6 +234,7 @@ Sources/Network/NetworkClient/RequestConvertible.swift, "Sources/Util/Extensions/Bundle+.swift", "Sources/Util/Extensions/Date+.swift", + "Sources/Util/Extensions/String+.swift", ); target = 16BE95272DED72FB006FFD00 /* UsecaseTest */; }; diff --git a/B.READ/B.READ/Sources/Data/Impls/QuoteRepositoryImpl.swift b/B.READ/B.READ/Sources/Data/Impls/QuoteRepositoryImpl.swift index d14b5e0f..bb88831f 100644 --- a/B.READ/B.READ/Sources/Data/Impls/QuoteRepositoryImpl.swift +++ b/B.READ/B.READ/Sources/Data/Impls/QuoteRepositoryImpl.swift @@ -12,6 +12,7 @@ import SwiftData actor QuoteRepositoryImpl: QuoteRepository { func createQuote(_ quote: Quote, in record: Record) throws { print("Impl: \(#function)") + if let _ = try findQuote(id: quote.id) { throw RepositoryError.dataAlreadyExist } @@ -24,6 +25,7 @@ actor QuoteRepositoryImpl: QuoteRepository { func updateQuote(_ quote: Quote) throws { print("Impl: \(#function)") + guard let dto = try findQuote(id: quote.id) else { throw RepositoryError.dataNotFound } @@ -36,6 +38,7 @@ actor QuoteRepositoryImpl: QuoteRepository { func deleteQuote(id: String) throws { print("Impl: \(#function)") + guard let dto = try findQuote(id: id) else { throw RepositoryError.dataNotFound } @@ -46,20 +49,18 @@ actor QuoteRepositoryImpl: QuoteRepository { func fetchQuotes(isbn: String) throws -> [Quote] { print("Impl: \(#function)") - do { - let descriptor = FetchDescriptor( - predicate: #Predicate { $0.isbn == isbn }, - sortBy: [.init(\.page, order: .forward)] - ) - let dtos = try modelContext.fetch(descriptor) - return dtos.map { $0.toEntity() } - } catch { - throw RepositoryError.fetchError - } + + let descriptor = FetchDescriptor( + predicate: #Predicate { $0.isbn == isbn }, + sortBy: [.init(\.page, order: .forward)] + ) + let dtos = try modelContext.fetch(descriptor) + return dtos.map { $0.toEntity() } } func fetchQuote(id: String) throws -> Quote { print("Impl: \(#function)") + guard let dto = try findQuote(id: id) else { throw RepositoryError.dataNotFound } @@ -68,15 +69,12 @@ actor QuoteRepositoryImpl: QuoteRepository { func fetchAllQuotes() throws -> [Quote] { print("Impl: \(#function)") - do { - let descriptor = FetchDescriptor( - sortBy: [.init(\.page, order: .forward)] - ) - let dtos = try modelContext.fetch(descriptor) - return dtos.map { $0.toEntity() } - } catch { - throw RepositoryError.fetchError - } + + let descriptor = FetchDescriptor( + sortBy: [.init(\.page, order: .forward)] + ) + let dtos = try modelContext.fetch(descriptor) + return dtos.map { $0.toEntity() } } } @@ -89,8 +87,10 @@ extension QuoteRepositoryImpl { /// - Returns: /// - `QuoteDTO?`: 조회된 첫 번째 문장 정보 DTO, 없으면 `nil` private func findQuote(id: String) throws -> QuoteDTO? { + let predicate = #Predicate { $0.id == id } + let descriptor = FetchDescriptor(predicate: predicate) + do { - let descriptor = FetchDescriptor(predicate: #Predicate { $0.id == id }) return try modelContext.fetch(descriptor).first } catch { throw RepositoryError.fetchError diff --git a/B.READ/B.READ/Sources/Domain/Dummy/DummyData.swift b/B.READ/B.READ/Sources/Domain/Dummy/DummyData.swift index 6c8ecfd5..e3d35a0f 100644 --- a/B.READ/B.READ/Sources/Domain/Dummy/DummyData.swift +++ b/B.READ/B.READ/Sources/Domain/Dummy/DummyData.swift @@ -144,110 +144,35 @@ extension DummyData { currentPage: 252, review: "", memos: [ - Memo( - id: "1", - isbn: "9788937460586", - createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 5, day: 4))!, - content: "싯다르타는 수많은 스승과 가르침을 거쳤지만, 결국 삶을 살아가는 과정에서 스스로 진리를 깨닫는다. 남의 말이 아닌, 체험이 지혜로 이어진다.", - pages: (100, 132), - guides: [Guide(date: fixedDate, content: "exmaple1"), Guide(date: fixedDate, content: "exmaple1")] - ), - Memo( - id: "2", - isbn: "9788937460586", - createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 5, day: 4))!, - content: "강을 바라보며 싯다르타는 모든 존재가 연결되어 흐르고 있다는 사실을 깨닫는다. 나 또한 변화와 흐름을 있는 그대로 받아들이고 싶어졌다.", - pages: (100, 132), - guides: [Guide(date: fixedDate, content: "exmaple1"), Guide(date: fixedDate, content: "exmaple1")] - ), - Memo( - id: "3", - isbn: "9788937460586", - createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 5, day: 4))!, - content: "싯다르타의 삶은 고통과 실수의 연속이었지만, 그 모든 것이 깨달음으로 나아가는 길이었다. 나 역시 내 방황을 긍정하고 싶어졌다.", - pages: (100, 132), - guides: [Guide(date: fixedDate, content: "exmaple1"), Guide(date: fixedDate, content: "exmaple1")] - ), +// Memo( +// id: "1", +// isbn: "9788937460586", +// createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 5, day: 4))!, +// content: "싯다르타는 수많은 스승과 가르침을 거쳤지만, 결국 삶을 살아가는 과정에서 스스로 진리를 깨닫는다. 남의 말이 아닌, 체험이 지혜로 이어진다.", +// pages: (100, 132), +// guides: [Guide(date: fixedDate, content: "exmaple1"), Guide(date: fixedDate, content: "exmaple1")] +// ), +// Memo( +// id: "2", +// isbn: "9788937460586", +// createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 5, day: 4))!, +// content: "강을 바라보며 싯다르타는 모든 존재가 연결되어 흐르고 있다는 사실을 깨닫는다. 나 또한 변화와 흐름을 있는 그대로 받아들이고 싶어졌다.", +// pages: (100, 132), +// guides: [Guide(date: fixedDate, content: "exmaple1"), Guide(date: fixedDate, content: "exmaple1")] +// ), +// Memo( +// id: "3", +// isbn: "9788937460586", +// createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 5, day: 4))!, +// content: "싯다르타의 삶은 고통과 실수의 연속이었지만, 그 모든 것이 깨달음으로 나아가는 길이었다. 나 역시 내 방황을 긍정하고 싶어졌다.", +// pages: (100, 132), +// guides: [Guide(date: fixedDate, content: "exmaple1"), Guide(date: fixedDate, content: "exmaple1")] +// ), ], quotes: [], createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 4, day: 19))!, updatedAt: Calendar.current.date(from: DateComponents(year: 2025, month: 5, day: 10))! ) - - -// Record( // 아주 작은 습관들 -// id: UUID().uuidString, -// isbn: "9791162540640", -// state: .reading, -// heartCount: 3, -// starCount: 0, -// isFavorite: true, -// period: (Calendar.current.date(from: DateComponents(year: 2025, month: 3, day: 27)), nil), -// currentPage: 321, -// review: "", -// summaryID: nil, -// memoIDs: [], -// quoteIDs: [], -// createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 3, day: 26))!, -// updatedAt: Calendar.current.date(from: DateComponents(year: 2025, month: 3, day: 26))! -// ), -// Record( // 위버멘쉬 -// id: UUID().uuidString, -// isbn: "9791192372730", -// state: .completed, -// heartCount: 3, -// starCount: 5, -// isFavorite: false, -// period: ( -// Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)), -// Calendar.current.date(from: DateComponents(year: 2025, month: 3, day: 5)) -// ), -// currentPage: 260, -// review: "", -// summaryID: nil, -// memoIDs: [], -// quoteIDs: [], -// createdAt: Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1))!, -// updatedAt: Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1))! -// ), -// Record( // 이기적 유전자 -// id: UUID().uuidString, -// isbn: "9788932473901", -// state: .completed, -// heartCount: 3, -// starCount: 2, -// isFavorite: false, -// period: ( -// Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)), -// Calendar.current.date(from: DateComponents(year: 2025, month: 3, day: 5)) -// ), -// currentPage: 632, -// review: "", -// summaryID: nil, -// memoIDs: [], -// quoteIDs: [], -// createdAt: Calendar.current.date(from: DateComponents(year: 2024, month: 12, day: 30))!, -// updatedAt: Calendar.current.date(from: DateComponents(year: 2024, month: 12, day: 30))! -// ), -// Record( // 듀얼 브레인 -// id: UUID().uuidString, -// isbn: "9791194368175", -// state: .completed, -// heartCount: 3, -// starCount: 4, -// isFavorite: false, -// period: ( -// Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)), -// Calendar.current.date(from: DateComponents(year: 2025, month: 3, day: 5)) -// ), -// currentPage: 308, -// review: "", -// summaryID: nil, -// memoIDs: [], -// quoteIDs: [], -// createdAt: Calendar.current.date(from: DateComponents(year: 2024, month: 12, day: 25))!, -// updatedAt: Calendar.current.date(from: DateComponents(year: 2024, month: 12, day: 25))! -// ) ] static var records: [Record] = [ diff --git a/B.READ/RepositoryTest/QuoteRepositoryTest.swift b/B.READ/RepositoryTest/QuoteRepositoryTest.swift index d94bbb71..6ef0cfa0 100644 --- a/B.READ/RepositoryTest/QuoteRepositoryTest.swift +++ b/B.READ/RepositoryTest/QuoteRepositoryTest.swift @@ -10,104 +10,186 @@ import Testing @testable import B_READ -//struct QuoteRepositoryTest { -// private let quoteRepository: QuoteRepository -// -// init() { -// // let stub = QuoteRepositoryStub() -// // quoteRepository = stub -// -// let storage = SwiftDataTestStorage() -// quoteRepository = QuoteRepositoryImpl(modelContainer: storage.modelContainer) -// } -// -// @Test("Quote Create and Fetch Test") -// func createQuote() async throws { -// try await quoteRepository.createQuote(DummyData.quote, in: <#Record#>) -// let fetched = try await quoteRepository.fetchQuote(id: DummyData.quote.id) -// #expect(fetched == DummyData.quote) -// } -// -// @Test("Quote Create Error Test - Data Already Exists") -// func createQuoteDataAlreadyExist() async throws { -// try await quoteRepository.createQuote(DummyData.quote, in: <#Record#>) -// -// await #expect(throws: RepositoryError.dataAlreadyExist, performing: { -// try await quoteRepository.createQuote(DummyData.quote, in: <#Record#>) -// }) -// } -// -// @Test("Error on Fetch by ID When Not Found") -// func fetchQuoteDataNotFound() async throws { -// await #expect(throws: RepositoryError.dataNotFound, performing: { -// _ = try await quoteRepository.fetchQuote(id: "non-existent-id") -// }) -// } -// -// @Test("Fetch Quotes by ISBN") -// func fetchQuotesByISBN() async throws { -// let other = Quote(id: "id-2", isbn: "999", content: "Other Book", page: 1) -// try await quoteRepository.createQuote(DummyData.quote, in: <#Record#>) -// try await quoteRepository.createQuote(other, in: <#Record#>) -// -// let fetchedList = try await quoteRepository.fetchQuotes(isbn: DummyData.quote.isbn) -// #expect(fetchedList == [DummyData.quote]) -// } -// -// @Test("Fetch All Quotes Test") -// func fetchAllQuotes() async throws { -// try await quoteRepository.createQuote(DummyData.quote, in: <#Record#>) -// let another = Quote(id: "id-2", isbn: DummyData.quote.isbn, content: "Additional Content", page: 5) -// try await quoteRepository.createQuote(another, in: <#Record#>) -// -// let all = try await quoteRepository.fetchAllQuotes() -// #expect(all.count == 2) -// } -// -// @Test("Quote Update Test") -// func updateQuote() async throws { -// try await quoteRepository.createQuote(DummyData.quote, in: <#Record#>) -// let updated = Quote( -// id: DummyData.quote.id, -// isbn: "updated-isbn", -// content: "새로운 내용", -// page: 99 -// ) -// try await quoteRepository.updateQuote(updated) -// let fetched = try await quoteRepository.fetchQuote(id: DummyData.quote.id) -// #expect(fetched == updated) -// } -// -// @Test("Quote Update Error Test - Data Not Found") -// func updateQuoteDataNotFound() async throws { -// -// let missing = Quote( -// id: "missing-id", -// isbn: "none", -// content: "none", -// page: 0 -// ) -// -// await #expect(throws: RepositoryError.dataNotFound, performing: { -// try await quoteRepository.updateQuote(missing) -// }) -// -// } -// -// @Test("Quote Delete Test") -// func deleteQuote() async throws { -// try await quoteRepository.createQuote(DummyData.quote, in: <#Record#>) -// try await quoteRepository.deleteQuote(id: DummyData.quote.id) -// -// await #expect(throws: RepositoryError.dataNotFound, performing: { -// _ = try await quoteRepository.fetchQuote(id: DummyData.quote.id) -// }) -// } -// -// @Test("Quote Delete Error Test - Data Not Found") -// func deleteQuoteDataNotFound() async throws { -// await #expect(throws: RepositoryError.dataNotFound, performing: { -// try await quoteRepository.deleteQuote(id: "missing-id") -// }) -// } -//} +struct QuoteRepositoryTest { + private let quoteRepository: QuoteRepository + private let recordRepository: RecordRepository + + init() { + // let stub = QuoteRepositoryStub() + // quoteRepository = stub + + let storage = SwiftDataTestStorage() + quoteRepository = QuoteRepositoryImpl(modelContainer: storage.modelContainer) + recordRepository = RecordRepositoryImpl(modelContainer: storage.modelContainer) + } + + @Test("Quote Create and Fetch Test") + func createQuote() async throws { + // 1. 생성할 레코드와 문장 + let dummyRecord = DummyData.dummyRecords[1] + let dummyQuote = DummyData.dummyQuote[0] + + // 2. 레코드를 생성 + try await recordRepository.createRecord(dummyRecord) + var fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + + // 3. 문장을 생성 + try await quoteRepository.createQuote(dummyQuote, in: fetchedRecord) + let fetchedQuote = try await quoteRepository.fetchQuote(id: dummyQuote.id) + + // 4. Quote 생성 및 패치 확인 + #expect(fetchedQuote == dummyQuote) + + // 5. Record에 저장됐는지 확인 + fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + #expect(fetchedRecord.quotes.first! == fetchedQuote) + } + + @Test("Quote Create Error Test - Data Already Exists") + func createQuoteDataAlreadyExist() async throws { + // 1. 생성할 레코드와 문장 + let dummyRecord = DummyData.dummyRecords[1] + let dummyQuote = DummyData.dummyQuote[0] + + // 2. 레코드를 생성 + try await recordRepository.createRecord(dummyRecord) + let fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + + // 3. 문장을 생성 + try await quoteRepository.createQuote(dummyQuote, in: fetchedRecord) + + // 4. 3번에서 생성한 문장을 한번 더 생성 -> Data Already Exist 에러 발생 + await #expect(throws: RepositoryError.dataAlreadyExist, performing: { + try await quoteRepository.createQuote(dummyQuote, in: fetchedRecord) + }) + } + + @Test("Quote Fetch Error Test - Data Not Found") + func fetchQuoteDataNotFound() async throws { + // 1. 데이터를 패치 -> Data Not Found 에러 발생 + await #expect(throws: RepositoryError.dataNotFound, performing: { + _ = try await quoteRepository.fetchQuote(id: "NotFoundId") + }) + } + + @Test("Fetch Quotes by ISBN") + func fetchQuotesByISBN() async throws { + // 1. 생성할 레코드와 문장 + let dummyRecord = DummyData.dummyRecords[1] + let dummyQuotes = [ + DummyData.dummyQuote[0], + DummyData.dummyQuote[1], + DummyData.dummyQuote[2] + ] + + // 2. 레코드를 생성 + try await recordRepository.createRecord(dummyRecord) + let fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + + // 3. 문장을 생성 + for quote in dummyQuotes { + try await quoteRepository.createQuote(quote, in: fetchedRecord) + } + + // 4. ISBN을 가지고 문장을 패치 + let fetchedQuotes = try await quoteRepository.fetchQuotes(isbn: fetchedRecord.isbn).sorted { $0.id < $1.id } + + // 5. 패치해온 문장과 더미 문장을 비교 + #expect(fetchedQuotes == dummyQuotes) + } + + @Test("Fetch All Quotes Test") + func fetchAllQuotes() async throws { + // 1. 생성할 레코드와 문장 + let dummyRecord = DummyData.dummyRecords[1] + let dummyQuotes = [ + DummyData.dummyQuote[0], + DummyData.dummyQuote[1], + DummyData.dummyQuote[2] + ] + + // 2. 레코드를 생성 + try await recordRepository.createRecord(dummyRecord) + let fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + + // 3. 문장을 생성 + for quote in dummyQuotes { + try await quoteRepository.createQuote(quote, in: fetchedRecord) + } + + // 4. ISBN을 가지고 문장을 패치 + let fetchedQuotes = try await quoteRepository.fetchAllQuotes().sorted { $0.id < $1.id } + + // 5. 패치해온 문장과 더미 문장을 비교 + #expect(fetchedQuotes == dummyQuotes) + } + + @Test("Quote Update Test") + func updateQuote() async throws { + + // 1. 생성할 레코드와 문장 + let dummyRecord = DummyData.dummyRecords[1] + var dummyQuote = DummyData.dummyQuote[0] + + // 2. 레코드를 생성 + try await recordRepository.createRecord(dummyRecord) + let fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + + // 3. 문장을 생성 + try await quoteRepository.createQuote(dummyQuote, in: fetchedRecord) + + // 4. 문장을 수정 + dummyQuote.content = "수정한 문장입니다." + dummyQuote.page = 100 + + // 5. 수정한 문장을 업데이트 + try await quoteRepository.updateQuote(dummyQuote) + + // 6. 문장을 패치 + let fetchedQuote = try await quoteRepository.fetchQuote(id: dummyQuote.id) + + #expect(fetchedQuote == dummyQuote) + } + + @Test("Quote Update Error Test - Data Not Found") + func updateQuoteDataNotFound() async throws { + // 1. 수정할 문장 + let dummyQuote = DummyData.dummyQuote[0] + + // 2. 문장 수정을 시도 -> Data Not Found 에러 발생 + await #expect(throws: RepositoryError.dataNotFound, performing: { + try await quoteRepository.updateQuote(dummyQuote) + }) + } + + @Test("Quote Delete Test") + func deleteQuote() async throws { + // 1. 생성할 레코드와 문장 + let dummyRecord = DummyData.dummyRecords[1] + let dummyQuote = DummyData.dummyQuote[0] + + // 2. 레코드를 생성 + try await recordRepository.createRecord(dummyRecord) + let fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + + // 3. 문장을 생성 + try await quoteRepository.createQuote(dummyQuote, in: fetchedRecord) + let fetchedQuote = try await quoteRepository.fetchQuote(id: dummyQuote.id) + + // 4. 문장을 삭제 + try await quoteRepository.deleteQuote(id: fetchedQuote.id) + + // 5. 패치를 시도 -> Data Not Found 에러 발생 + await #expect(throws: RepositoryError.dataNotFound, performing: { + _ = try await quoteRepository.fetchQuote(id: fetchedQuote.id) + }) + } + + @Test("Quote Delete Error Test - Data Not Found") + func deleteQuoteDataNotFound() async throws { + // 1. 삭제를 시도 -> Data Not Found 에러 발생 + await #expect(throws: RepositoryError.dataNotFound, performing: { + try await quoteRepository.deleteQuote(id: "NotFoundId") + }) + } +} diff --git a/B.READ/RepositoryTest/RecordRepositoryTest.swift b/B.READ/RepositoryTest/RecordRepositoryTest.swift index 33938d5d..50815d4c 100644 --- a/B.READ/RepositoryTest/RecordRepositoryTest.swift +++ b/B.READ/RepositoryTest/RecordRepositoryTest.swift @@ -13,10 +13,13 @@ import Testing struct RecordRepositoryTest { private let recordRepository: RecordRepository + private let memoRepository: MemoRepository + init() { // recordRepository = RecordRepositoryStub() let storage = SwiftDataTestStorage() recordRepository = RecordRepositoryImpl(modelContainer: storage.modelContainer) + memoRepository = MemoRepositoryImpl(modelContainer: storage.modelContainer) } @Test("Record Create Test") @@ -94,13 +97,24 @@ struct RecordRepositoryTest { @Test("Recent Finished Record without Summary Fetch Test") func fetchRecordAvailableForSummary() async throws { - let record = DummyData.dummyRecords[2] + // 1. 생성할 레코드와 메모 + let dummyRecord = DummyData.dummyRecords[2] + let dummyMemo = DummyData.dummyMemos[0] - try await recordRepository.createRecord(record) + // 2. 레코드를 생성 + try await recordRepository.createRecord(dummyRecord) + var fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + + // 3. 메모를 생성 + try await memoRepository.createMemo(dummyMemo, in: fetchedRecord) + + // 4. 메모가 생성된 레코드를 패치 + fetchedRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) - let fetchedRecord = try await recordRepository.fetchRecordAvailableForSummary() + // 5. 요약가능한 독서기록을 패치 + let availableRecord = try await recordRepository.fetchRecordAvailableForSummary() - #expect(fetchedRecord == record) + #expect(availableRecord == fetchedRecord) } @Test("Recent Finished Record without Summary Fetch Test - Data Not Found") diff --git a/B.READ/RepositoryTest/SummaryRepositoryTest.swift b/B.READ/RepositoryTest/SummaryRepositoryTest.swift index 33064447..85614a24 100644 --- a/B.READ/RepositoryTest/SummaryRepositoryTest.swift +++ b/B.READ/RepositoryTest/SummaryRepositoryTest.swift @@ -1,104 +1,104 @@ -//// -//// SummaryRepositoryTest.swift -//// RepositoryTest -//// -//// Created by 김도연 on 6/9/25. -//// // -//import Foundation -//import Testing +// SummaryRepositoryTest.swift +// RepositoryTest // -//@testable import B_READ +// Created by 김도연 on 6/9/25. // -//struct SummaryRepositoryTest { -// -// private let recordRepository: RecordRepository -// private let summaryRepository: SummaryRepository -// -// init() { -// let storage = SwiftDataTestStorage() -// self.recordRepository = RecordRepositoryImpl(modelContainer: storage.modelContainer) -// self.summaryRepository = SummaryRepositoryImpl(modelContainer: storage.modelContainer) -// } -// -// @Test("Summary Create Test") -// func createSummary() async throws { -// // Given -// let dummyRecord = DummyData.dummyRecords.first! -// try await recordRepository.createRecord(dummyRecord) -// let targetRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) -// let dummySummary = DummyData.summary1 -// -// // When -// try await summaryRepository.createSummary(dummySummary, in: targetRecord) -// let fetchedSummary = try await summaryRepository.fetchSummary(id: dummySummary.id) -// -// // Then -// #expect(fetchedSummary.id == dummySummary.id) -// #expect(fetchedSummary.tags.count == dummySummary.tags.count) -// } -// -// @Test("Summary Create Error Test - Data Already Exists") -// func createSummaryDataAlreadyExists() async throws { -// // Given -// let dummyRecord = DummyData.dummyRecords.first! -// try await recordRepository.createRecord(dummyRecord) -// let targetRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) -// let dummySummary = DummyData.summary1 -// try await summaryRepository.createSummary(dummySummary, in: targetRecord) -// -// // When + Then -// await #expect(throws: RepositoryError.dataAlreadyExist, performing: { -// try await summaryRepository.createSummary(dummySummary, in: targetRecord) -// }) -// } -// -// @Test("Summary Fetch Error Test - Data Not Found") -// func fetchSummaryDataNotFound() async throws { -// // When + Then -// await #expect(throws: RepositoryError.dataNotFound, performing: { -// _ = try await summaryRepository.fetchSummary(id: "non-existent-id") -// }) -// } -// -// @Test("Summary Fetch All Test") -// func fetchAllSummaries() async throws { -// // Given -// for record in DummyData.dummyRecords { -// try await recordRepository.createRecord(record) -// } -// -// for i in 0.. $1.createdAt } -// let actualSummaries = fetchedSummaries.sorted { $0.createdAt > $1.createdAt } -// #expect(expectedSummaries.first?.id == actualSummaries.first?.id) -// #expect(expectedSummaries.count == actualSummaries.count) -// } -// -//} -// -//// MARK: - Entity Extensions -//extension AlanSummary: Equatable { -// public static func == (lhs: AlanSummary, rhs: AlanSummary) -> Bool { -// return lhs.id == rhs.id && -// lhs.isbn == rhs.isbn && -// lhs.content == rhs.content && -// lhs.tags == rhs.tags && -// lhs.createdAt == rhs.createdAt -// } -//} -// -//extension Tag: Equatable { -// public static func == (lhs: Tag, rhs: Tag) -> Bool { -// return lhs.id == rhs.id && -// lhs.content == rhs.content -// } -//} + +import Foundation +import Testing + +@testable import B_READ + +struct SummaryRepositoryTest { + + private let recordRepository: RecordRepository + private let summaryRepository: SummaryRepository + + init() { + let storage = SwiftDataTestStorage() + self.recordRepository = RecordRepositoryImpl(modelContainer: storage.modelContainer) + self.summaryRepository = SummaryRepositoryImpl(modelContainer: storage.modelContainer) + } + + @Test("Summary Create Test") + func createSummary() async throws { + // Given + let dummyRecord = DummyData.dummyRecords.first! + try await recordRepository.createRecord(dummyRecord) + let targetRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + let dummySummary = DummyData.summary1 + + // When + try await summaryRepository.createSummary(dummySummary, in: targetRecord) + let fetchedSummary = try await summaryRepository.fetchSummary(id: dummySummary.id) + + // Then + #expect(fetchedSummary.id == dummySummary.id) + #expect(fetchedSummary.tags.count == dummySummary.tags.count) + } + + @Test("Summary Create Error Test - Data Already Exists") + func createSummaryDataAlreadyExists() async throws { + // Given + let dummyRecord = DummyData.dummyRecords.first! + try await recordRepository.createRecord(dummyRecord) + let targetRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) + let dummySummary = DummyData.summary1 + try await summaryRepository.createSummary(dummySummary, in: targetRecord) + + // When + Then + await #expect(throws: RepositoryError.dataAlreadyExist, performing: { + try await summaryRepository.createSummary(dummySummary, in: targetRecord) + }) + } + + @Test("Summary Fetch Error Test - Data Not Found") + func fetchSummaryDataNotFound() async throws { + // When + Then + await #expect(throws: RepositoryError.dataNotFound, performing: { + _ = try await summaryRepository.fetchSummary(id: "non-existent-id") + }) + } + + @Test("Summary Fetch All Test") + func fetchAllSummaries() async throws { + // Given + for record in DummyData.dummyRecords { + try await recordRepository.createRecord(record) + } + + for i in 0.. $1.createdAt } + let actualSummaries = fetchedSummaries.sorted { $0.createdAt > $1.createdAt } + #expect(expectedSummaries.first?.id == actualSummaries.first?.id) + #expect(expectedSummaries.count == actualSummaries.count) + } + +} + +// MARK: - Entity Extensions +extension AlanSummary: Equatable { + public static func == (lhs: AlanSummary, rhs: AlanSummary) -> Bool { + return lhs.id == rhs.id && + lhs.isbn == rhs.isbn && + lhs.content == rhs.content && + lhs.tags == rhs.tags && + lhs.createdAt == rhs.createdAt + } +} + +extension Tag: Equatable { + public static func == (lhs: Tag, rhs: Tag) -> Bool { + return lhs.id == rhs.id && + lhs.content == rhs.content + } +} diff --git a/B.READ/RepositoryTest/UserInfoRepositoryTest.swift b/B.READ/RepositoryTest/UserInfoRepositoryTest.swift index 638b00c2..a3bcc919 100644 --- a/B.READ/RepositoryTest/UserInfoRepositoryTest.swift +++ b/B.READ/RepositoryTest/UserInfoRepositoryTest.swift @@ -78,7 +78,12 @@ struct UserInfoRepositoryTest { try await userInfoRepository.updateUserInfo(updatedUserInfo) - let fetchedUserInfo = try await userInfoRepository.fetchUserInfo() + var fetchedUserInfo = try await userInfoRepository.fetchUserInfo() + + // 카테고리 순서 정렬 + updatedUserInfo.categories = updatedUserInfo.categories.sorted { $0.id < $1.id } + fetchedUserInfo.categories = fetchedUserInfo.categories.sorted { $0.id < $1.id } + #expect(fetchedUserInfo == updatedUserInfo) } diff --git a/B.READ/ServiceTest/BookServiceTest.swift b/B.READ/ServiceTest/BookServiceTest.swift index 21d2e5c4..f9386beb 100644 --- a/B.READ/ServiceTest/BookServiceTest.swift +++ b/B.READ/ServiceTest/BookServiceTest.swift @@ -92,31 +92,4 @@ struct BookServiceTest { } } -// @Test("Error: Missing Query") -// func testMissingQueryErrorDecoding() async throws { -// let mockClient = MockNetworkClient( -// nextMockFileName: "MissingQuery", -// shouldReturnError: false -// ) -// let aladinService = AladinService(client: mockClient) -// -// do { -// _ = try await aladinService.fetchBookList(for: "", index: 1) -// #expect(false) -// } catch let error as AladinError { -// switch error { -// case .serverError(let code, let message): -// #expect(code == 3) -// #expect(message == "검색어를 입력해주세요.") -// print("✅ Correctly decoded Aladin error: [\(code)] \(message)") -// default: -// print("Expected serverError, but got \(error)") -// #expect(false) -// } -// } catch { -// print("Unexpected error type: \(error)") -// #expect(false) -// } -// } - } diff --git a/B.READ/UsecaseTest/LibraryUseCaseTest.swift b/B.READ/UsecaseTest/LibraryUseCaseTest.swift index 3deaa428..d3c0be9e 100644 --- a/B.READ/UsecaseTest/LibraryUseCaseTest.swift +++ b/B.READ/UsecaseTest/LibraryUseCaseTest.swift @@ -37,26 +37,35 @@ struct LibraryUseCaseTest { @Test("Save/Load Record Test") func loadRecordTest() async throws { - // 1. 레코드, 책 정보 + // 1. 유저 정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + + // 2. 레코드, 책 정보 let book = DummyData.dummyBooks[0] let record = DummyData.dummyRecords[0] - // 2. 레코드, 책 생성 + // 3. 레코드, 책 생성 try await libraryUseCase.saveRecord(record: record, book: book) - // 3. 레코드 책 패치 + // 4. 레코드 책 패치 let fetchedInfo = try await libraryUseCase.loadRecord(record.id) #expect(fetchedInfo.0.id == record.id) #expect(fetchedInfo.0.createdAt == record.createdAt) } + @Test("Save/Load Record Tests", arguments: [ (DummyData.dummyBooks[0], DummyData.dummyRecords[0]), (DummyData.dummyBooks[1], DummyData.dummyRecords[1]), (DummyData.dummyBooks[2], DummyData.dummyRecords[2]) ]) func loadRecordTest(book: Book, record: Record) async throws { + // 1. 유저 정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + // 2. 레코드, 책 생성 try await libraryUseCase.saveRecord(record: record, book: book) @@ -69,7 +78,11 @@ struct LibraryUseCaseTest { @Test("Load Record List Test") func loadRecordListTest() async throws { - // 1. 레코드들 생성 + // 1. 유저 정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + + // 2. 레코드들 생성 let infos: [(Record, Book)] = [ (DummyData.dummyRecords[0], DummyData.dummyBooks[0]), (DummyData.dummyRecords[1], DummyData.dummyBooks[1]), @@ -79,7 +92,7 @@ struct LibraryUseCaseTest { try await libraryUseCase.saveRecord(record: info.0, book: info.1) } - // 2. 레코드들 패치 + // 3. 레코드들 패치 let fetchedInfos = try await libraryUseCase.loadRecordList() .sorted { $0.0.createdAt > $1.0.createdAt } for (info, fetchedInfo) in zip(infos, fetchedInfos) { @@ -91,17 +104,21 @@ struct LibraryUseCaseTest { @Test("Delete Record Test") func deleteRecordTest() async throws { - // 1. 레코드 정보 + // 1. 유저 정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + + // 2. 레코드 정보 let book = DummyData.dummyBooks[0] let record = DummyData.dummyRecords[0] - // 2. 레코드, 책 생성 + // 3. 레코드, 책 생성 try await libraryUseCase.saveRecord(record: record, book: book) - // 3. 레코드 삭제 + // 4. 레코드 삭제 try await libraryUseCase.deleteRecord(record) - // 4. 레코드 패치 + // 5. 레코드 패치 await #expect(throws: RepositoryError.dataNotFound, performing: { let _ = try await libraryUseCase.loadRecord(record.id) }) @@ -109,22 +126,26 @@ struct LibraryUseCaseTest { @Test("Edit Record Test") func editRecordTest() async throws { - // 1. 레코드 정보 + // 1. 유저 정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + + // 2. 레코드 정보 let book = DummyData.dummyBooks[0] var record = DummyData.dummyRecords[0] - // 2. 레코드, 책 생성 + // 3. 레코드, 책 생성 try await libraryUseCase.saveRecord(record: record, book: book) - // 3. 레코드 정보 수정 + // 4. 레코드 정보 수정 record.isFavorite = true record.state = .completed record.starCount = 5 - // 4. 수정한 레코드 업데이트 + // 5. 수정한 레코드 업데이트 try await libraryUseCase.editRecord(record) - // 5. 레코드, 책 패치 + // 6. 레코드, 책 패치 let fetchedInfo = try await libraryUseCase.loadRecord(record.id) #expect(fetchedInfo.0.id == record.id) @@ -135,22 +156,27 @@ struct LibraryUseCaseTest { @Test("Edit Record Test - Record Data Not Found") func editRecordDataNotFoundTest() async throws { - // 1. 레코드 정보 + + // 1. 유저 정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + + // 2. 레코드 정보 let book = DummyData.dummyBooks[0] var record = DummyData.dummyRecords[0] - // 2. 도서만 생성 + // 3. 도서만 생성 try await bookRepository.createBook(book) - // 3. 레코드 정보 수정 + // 4. 레코드 정보 수정 record.isFavorite = true record.state = .completed record.starCount = 5 - // 4. 수정한 레코드 업데이트 + // 5. 수정한 레코드 업데이트 try await libraryUseCase.editRecord(record) - // 5. 레코드, 책 패치 + // 6. 레코드, 책 패치 let fetchedInfo = try await libraryUseCase.loadRecord(record.id) #expect(fetchedInfo.0.id == record.id) @@ -161,7 +187,12 @@ struct LibraryUseCaseTest { @Test("loadRecentUpdatedReadingRecord Test") func loadRecentUpdatedReadingRecordTest() async throws { - // 1. 레코드들 생성 + + // 1. 유저 정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + + // 2. 레코드들 생성 let infos: [(Record, Book)] = [ (DummyData.dummyRecords[0], DummyData.dummyBooks[0]), (DummyData.dummyRecords[1], DummyData.dummyBooks[1]), @@ -171,10 +202,10 @@ struct LibraryUseCaseTest { try await libraryUseCase.saveRecord(record: info.0, book: info.1) } - // 2. 최근 업데이트한 읽는 중 상태의 독서 기록 패치 + // 3. 최근 업데이트한 읽는 중 상태의 독서 기록 패치 let fetchedList = try await libraryUseCase.loadRecentUpdatedReadingRecord(maxCount: 3) - // 3. 기대되는 결과 + // 4. 기대되는 결과 let predictList = Array(infos .filter { $0.0.state == .reading } .sorted { $0.0.updatedAt > $1.0.updatedAt } diff --git a/B.READ/UsecaseTest/MemoUseCaseTest.swift b/B.READ/UsecaseTest/MemoUseCaseTest.swift index 9b519048..00c91aca 100644 --- a/B.READ/UsecaseTest/MemoUseCaseTest.swift +++ b/B.READ/UsecaseTest/MemoUseCaseTest.swift @@ -28,13 +28,17 @@ struct MemoUseCaseTest { userInfoRepository: userInfoRepository, bookRepository: BookRepositoryImpl(modelContainer: storage.modelContainer), memoRepository: memoRepository, - aiService: AlanService() + aiService: AlanService(), + bookService: AladinService() ) } @Test("Memo Create & Fetch Test") func saveMemoTestCreate() async throws { + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + let dummyRecord = DummyData.dummyRecords.first! try await recordRepository.createRecord(dummyRecord) let targetRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) @@ -48,6 +52,9 @@ struct MemoUseCaseTest { @Test("Memo Update Test") func saveMemoTestUpdate() async throws { + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + let dummyRecord = DummyData.dummyRecords.first! try await recordRepository.createRecord(dummyRecord) let targetRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) @@ -65,6 +72,9 @@ struct MemoUseCaseTest { @Test("Memo delete Test") func deleteMemoTestUpdate() async throws { + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + let dummyRecord = DummyData.dummyRecords.first! try await recordRepository.createRecord(dummyRecord) let targetRecord = try await recordRepository.fetchRecord(id: dummyRecord.id) diff --git a/B.READ/UsecaseTest/QuoteUseCaseTest.swift b/B.READ/UsecaseTest/QuoteUseCaseTest.swift index 560d7efb..03657087 100644 --- a/B.READ/UsecaseTest/QuoteUseCaseTest.swift +++ b/B.READ/UsecaseTest/QuoteUseCaseTest.swift @@ -28,12 +28,17 @@ struct QuoteUseCaseTest { self.quoteUseCase = QuoteUseCaseImpl( userInfoRepository: userInfoRepository, quoteRepository: quoteRepository, - bookRepository: bookRepository + bookRepository: bookRepository, + bookService: AladinService() ) } @Test("Quote Create & Id Fetch Test") func saveQuoteTestCreate() async throws { + // 0. 유저정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + let dummyRecord = DummyData.dummyRecords[1] let dummyQuote = DummyData.dummyQuote[0] @@ -53,6 +58,10 @@ struct QuoteUseCaseTest { @Test("Quote Update Test") func saveQuoteTestUpdate() async throws { + // 0. 유저정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + let dummyRecord = DummyData.dummyRecords[1] var dummyQuote = DummyData.dummyQuote[0] @@ -79,6 +88,10 @@ struct QuoteUseCaseTest { @Test("Quote Remove & AllCase Fetch Test") func removeQuoteTest() async throws { + // 0. 유저정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + let dummyRecord = DummyData.dummyRecords[1] let dummyQuote = DummyData.dummyQuote[0] @@ -109,6 +122,10 @@ struct QuoteUseCaseTest { @Test("Load Book Title Test") func loadBookTitleTest() async throws { + // 0. 유저정보 생성 + let userInfo = DummyData.userInfo + try await userInfoRepository.createUserInfo(userInfo) + let dummyBook = DummyData.dummyBooks[1] let dummyRecord = DummyData.dummyRecords[1]