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
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ private extension AppGraph {
.saveDevelopmentRecordDraftUseCase,
confirmRecordUseCase: developmentGraphSet
.developmentRecordMutationUseCaseGraph
.confirmDevelopmentRecordUseCase
.confirmDevelopmentRecordUseCase,
restoreRecordUseCase: developmentGraphSet
.developmentRecordMutationUseCaseGraph
.restoreDevelopmentRecordUseCase
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public protocol RestoreDevelopmentRecordUseCase {
func execute(
goalId: String,
recordId: String,
versionId: String,
sourceVersionId: String
) async throws -> DevelopmentRecord.Version
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,39 @@
// Created by opfic on 8/27/26.
//

import Foundation

public final class RestoreDevelopmentRecordUseCaseImpl: RestoreDevelopmentRecordUseCase {
private let repository: DevelopmentRecordRepository
private let goalRepository: DevelopmentGoalRepository
private let idProvider: () -> String

init(
_ repository: DevelopmentRecordRepository,
_ goalRepository: DevelopmentGoalRepository,
idProvider: @escaping () -> String = { UUID().uuidString }
_ goalRepository: DevelopmentGoalRepository
) {
self.repository = repository
self.goalRepository = goalRepository
self.idProvider = idProvider
}

public func execute(
goalId: String,
recordId: String,
versionId: String,
sourceVersionId: String
) async throws -> DevelopmentRecord.Version {
let record = try await repository.fetchRecord(goalId: goalId, recordId: recordId)
if record.currentVersion?.id == versionId {
return try await restoredVersion(
record: record,
goalId: goalId,
recordId: recordId,
versionId: versionId,
sourceVersionId: sourceVersionId
)
}

let goal = try await goalRepository.fetchGoal(goalId)
guard goal.status == .inProgress else {
throw DomainLayerError.developmentGoalIsNotInProgress
}

let record = try await repository.fetchRecord(goalId: goalId, recordId: recordId)
guard record.id == recordId, record.goalId == goalId else {
throw DomainLayerError.invalidData(context: "developmentRecord")
}
Expand All @@ -52,11 +57,52 @@ public final class RestoreDevelopmentRecordUseCaseImpl: RestoreDevelopmentRecord
throw DomainLayerError.developmentRecordVersionNotFound
}

return try await repository.restoreVersion(
do {
return try await repository.restoreVersion(
goalId: goalId,
recordId: recordId,
versionId: versionId,
sourceVersionId: sourceVersionId
)
} catch {
guard let record = try? await repository.fetchRecord(
goalId: goalId,
recordId: recordId
), let version = try? await restoredVersion(
record: record,
goalId: goalId,
recordId: recordId,
versionId: versionId,
sourceVersionId: sourceVersionId
) else {
throw error
}
return version
}
}
}

private extension RestoreDevelopmentRecordUseCaseImpl {
func restoredVersion(
record: DevelopmentRecord,
goalId: String,
recordId: String,
versionId: String,
sourceVersionId: String
) async throws -> DevelopmentRecord.Version {
guard record.currentVersion?.id == versionId else {
throw DomainLayerError.developmentRecordVersionNotFound
}
let version = try await repository.fetchVersion(
goalId: goalId,
recordId: recordId,
versionId: idProvider(),
sourceVersionId: sourceVersionId
versionId: versionId
)
guard version.recordId == recordId,
version.kind == .rollback,
version.sourceVersionId == sourceVersionId else {
throw DomainLayerError.developmentRecordVersionNotFound
}
return version
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ actor DevelopmentRecordRepositorySpy: DevelopmentRecordRepository {
private let confirmedVersion: DevelopmentRecord.Version?
private let confirmError: Error?
private let restoredVersion: DevelopmentRecord.Version?
private let restoreError: Error?
private var recordedCreateRequests = [CreateRequest]()
private var recordedRecordQueries = [String]()
private var recordedVersionQueries = [RecordQuery]()
Expand All @@ -109,7 +110,8 @@ actor DevelopmentRecordRepositorySpy: DevelopmentRecordRepository {
savedRecord: DevelopmentRecord? = nil,
confirmedVersion: DevelopmentRecord.Version? = nil,
confirmError: Error? = nil,
restoredVersion: DevelopmentRecord.Version? = nil
restoredVersion: DevelopmentRecord.Version? = nil,
restoreError: Error? = nil
) {
self.createResult = createResult
self.records = records
Expand All @@ -119,6 +121,7 @@ actor DevelopmentRecordRepositorySpy: DevelopmentRecordRepository {
self.confirmedVersion = confirmedVersion
self.confirmError = confirmError
self.restoredVersion = restoredVersion
self.restoreError = restoreError
}

func createRecord(
Expand Down Expand Up @@ -215,6 +218,9 @@ actor DevelopmentRecordRepositorySpy: DevelopmentRecordRepository {
.init(
goalId: goalId, recordId: recordId, versionId: versionId, sourceVersionId: sourceVersionId)
)
if let restoreError {
throw restoreError
}
return try requiredDevelopmentRecordRepositoryResult(restoredVersion)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,124 +237,4 @@ struct DevelopmentRecordUseCaseTests {
])
}

@Test("같은 기록의 과거 버전은 rollback 버전으로 되돌린다")
func 같은_기록의_과거_버전은_rollback_버전으로_되돌린다() async throws {
let goal = try makeDevelopmentRecordGoal()
let currentVersion = try DevelopmentRecord.CurrentVersion(id: "version-3", number: 3)
let record = try makeDevelopmentRecord(currentVersion: currentVersion, draft: nil)
let initialVersion = try makeDevelopmentRecordInitialVersion()
let correctionVersion = try makeDevelopmentRecordCorrectionVersion(
id: "version-2",
sourceVersionId: "version-1"
)
let currentRecordVersion = try makeDevelopmentRecordCorrectionVersion(
id: "version-3",
number: 3,
sourceVersionId: "version-2"
)
let rollbackVersion = try DevelopmentRecord.Version(
id: "version-4",
recordId: "record-1",
number: 4,
title: "되돌린 기록",
markdownContent: "본문",
kind: .rollback,
sourceVersionId: "version-1",
confirmedAt: .distantFuture
)
let repository = DevelopmentRecordRepositorySpy(
record: record,
versions: [initialVersion, correctionVersion, currentRecordVersion],
restoredVersion: rollbackVersion
)
let useCase = RestoreDevelopmentRecordUseCaseImpl(
repository,
DevelopmentRecordGoalRepositorySpy(goal: goal),
idProvider: { "version-4" }
)
let result = try await useCase.execute(
goalId: "goal-1",
recordId: "record-1",
sourceVersionId: "version-1"
)
#expect(result == rollbackVersion)
#expect(await repository.restoreRequests() == [
.init(
goalId: "goal-1",
recordId: "record-1",
versionId: "version-4",
sourceVersionId: "version-1"
)
])
}

@Test("현재 또는 다른 기록의 버전은 되돌리기 대상으로 거부한다")
func 현재_또는_다른_기록의_버전은_되돌리기_대상으로_거부한다() async throws {
let goal = try makeDevelopmentRecordGoal()
let currentVersion = try DevelopmentRecord.CurrentVersion(id: "version-2", number: 2)
let record = try makeDevelopmentRecord(currentVersion: currentVersion, draft: nil)
let currentRecordVersion = try makeDevelopmentRecordCorrectionVersion(
id: "version-2",
number: 2,
sourceVersionId: "version-1"
)
let otherRecordVersion = try DevelopmentRecord.Version(
id: "other-version-1",
recordId: "record-2",
number: 1,
title: "다른 기록",
markdownContent: "본문",
kind: .initial,
sourceVersionId: nil,
confirmedAt: .distantPast
)
for sourceVersionId in [currentRecordVersion.id, otherRecordVersion.id] {
let repository = DevelopmentRecordRepositorySpy(
record: record,
versions: [currentRecordVersion, otherRecordVersion]
)
let useCase = RestoreDevelopmentRecordUseCaseImpl(
repository,
DevelopmentRecordGoalRepositorySpy(goal: goal)
)

await expectDevelopmentRecordDomainError(.developmentRecordVersionNotFound) {
try await useCase.execute(
goalId: "goal-1",
recordId: "record-1",
sourceVersionId: sourceVersionId
)
}
#expect(await repository.restoreRequests().isEmpty)
}
}

@Test("Draft가 있으면 버전 되돌리기를 거부한다")
func Draft가_있으면_버전_되돌리기를_거부한다() async throws {
let goal = try makeDevelopmentRecordGoal()
let currentVersion = try DevelopmentRecord.CurrentVersion(id: "version-1", number: 1)
let record = try makeDevelopmentRecord(
currentVersion: currentVersion,
draft: DevelopmentRecord.Draft(
title: "정정 초안",
markdownContent: "본문",
baseVersionId: currentVersion.id,
updatedAt: .distantPast
)
)
let repository = DevelopmentRecordRepositorySpy(record: record)
let useCase = RestoreDevelopmentRecordUseCaseImpl(
repository,
DevelopmentRecordGoalRepositorySpy(goal: goal)
)

await expectDevelopmentRecordDomainError(.developmentRecordDraftConflict) {
try await useCase.execute(
goalId: "goal-1",
recordId: "record-1",
sourceVersionId: "version-1"
)
}
#expect(await repository.restoreRequests().isEmpty)
}
}
Loading