Skip to content

Commit 47618d2

Browse files
committed
fix: Todo 변경 시 위젯 동기화 트리거 추가
1 parent d700ad9 commit 47618d2

3 files changed

Lines changed: 174 additions & 2 deletions

File tree

Application/DevLogData/Sources/DataAssembler.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public final class DataAssembler: Assembler {
3535
container.register(TodoRepository.self) {
3636
TodoRepositoryImpl(
3737
todoService: container.resolve(TodoService.self),
38-
todoCategoryService: container.resolve(TodoCategoryService.self)
38+
todoCategoryService: container.resolve(TodoCategoryService.self),
39+
widgetSyncEventBus: container.resolve(WidgetSyncEventBus.self)
3940
)
4041
}
4142

Application/DevLogData/Sources/Repository/TodoRepositoryImpl.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import DevLogDomain
1212
final class TodoRepositoryImpl: TodoRepository {
1313
private let todoService: TodoService
1414
private let todoCategoryService: TodoCategoryService
15+
private let widgetSyncEventBus: WidgetSyncEventBus
1516

1617
init(
1718
todoService: TodoService,
18-
todoCategoryService: TodoCategoryService
19+
todoCategoryService: TodoCategoryService,
20+
widgetSyncEventBus: WidgetSyncEventBus
1921
) {
2022
self.todoService = todoService
2123
self.todoCategoryService = todoCategoryService
24+
self.widgetSyncEventBus = widgetSyncEventBus
2225
}
2326

2427
func fetchTodos(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage {
@@ -105,6 +108,7 @@ final class TodoRepositoryImpl: TodoRepository {
105108
let request = TodoRequest.fromDomain(todo)
106109
do {
107110
try await todoService.upsertTodo(request: request)
111+
widgetSyncEventBus.publish(.syncRequested)
108112
} catch {
109113
throw error.toDomain()
110114
}
@@ -113,6 +117,7 @@ final class TodoRepositoryImpl: TodoRepository {
113117
func deleteTodo(_ todoId: String) async throws {
114118
do {
115119
try await todoService.deleteTodo(todoId: todoId)
120+
widgetSyncEventBus.publish(.syncRequested)
116121
} catch {
117122
throw error.toDomain()
118123
}
@@ -121,6 +126,7 @@ final class TodoRepositoryImpl: TodoRepository {
121126
func undoDeleteTodo(_ todoId: String) async throws {
122127
do {
123128
try await todoService.undoDeleteTodo(todoId: todoId)
129+
widgetSyncEventBus.publish(.syncRequested)
124130
} catch {
125131
throw error.toDomain()
126132
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
//
2+
// TodoRepositoryImplTests.swift
3+
// DevLogDataTests
4+
//
5+
// Created by opfic on 6/1/26.
6+
//
7+
8+
import Combine
9+
import Foundation
10+
import Testing
11+
import DevLogCore
12+
import DevLogDomain
13+
@testable import DevLogData
14+
15+
struct TodoRepositoryImplTests {
16+
@Test("Todo 변경 성공 시 위젯 동기화 이벤트를 발행한다")
17+
func todo_변경_성공_시_위젯_동기화_이벤트를_발행한다() async throws {
18+
let fixture = makeFixture()
19+
let todo = makeTodo()
20+
21+
try await fixture.repository.upsertTodo(todo)
22+
try await fixture.repository.deleteTodo(todo.id)
23+
try await fixture.repository.undoDeleteTodo(todo.id)
24+
25+
let events = fixture.widgetSyncEventBus.events
26+
#expect(events == [.syncRequested, .syncRequested, .syncRequested])
27+
}
28+
29+
@Test("Todo 변경 실패 시 위젯 동기화 이벤트를 발행하지 않는다")
30+
func todo_변경_실패_시_위젯_동기화_이벤트를_발행하지_않는다() async throws {
31+
let fixture = makeFixture()
32+
let todo = makeTodo()
33+
34+
await fixture.todoService.setShouldFail(true)
35+
36+
do {
37+
try await fixture.repository.upsertTodo(todo)
38+
Issue.record("upsertTodo should fail")
39+
} catch {
40+
#expect(error as? TodoRepositoryImplTestsError == .serviceFailed)
41+
}
42+
do {
43+
try await fixture.repository.deleteTodo(todo.id)
44+
Issue.record("deleteTodo should fail")
45+
} catch {
46+
#expect(error as? TodoRepositoryImplTestsError == .serviceFailed)
47+
}
48+
do {
49+
try await fixture.repository.undoDeleteTodo(todo.id)
50+
Issue.record("undoDeleteTodo should fail")
51+
} catch {
52+
#expect(error as? TodoRepositoryImplTestsError == .serviceFailed)
53+
}
54+
55+
let events = fixture.widgetSyncEventBus.events
56+
#expect(events.isEmpty)
57+
}
58+
59+
private func makeFixture() -> Fixture {
60+
let todoService = TodoServiceSpy()
61+
let todoCategoryService = TodoCategoryServiceSpy()
62+
let widgetSyncEventBus = WidgetSyncEventBusSpy()
63+
let repository = TodoRepositoryImpl(
64+
todoService: todoService,
65+
todoCategoryService: todoCategoryService,
66+
widgetSyncEventBus: widgetSyncEventBus
67+
)
68+
69+
return Fixture(
70+
repository: repository,
71+
todoService: todoService,
72+
widgetSyncEventBus: widgetSyncEventBus
73+
)
74+
}
75+
76+
private func makeTodo() -> Todo {
77+
Todo(
78+
id: "todo-id",
79+
isPinned: false,
80+
isCompleted: false,
81+
isChecked: false,
82+
number: 1,
83+
title: "title",
84+
content: "content",
85+
createdAt: .now,
86+
updatedAt: .now,
87+
completedAt: nil,
88+
deletedAt: nil,
89+
dueDate: nil,
90+
tags: [],
91+
category: .system(.feature)
92+
)
93+
}
94+
}
95+
96+
private struct Fixture {
97+
let repository: TodoRepositoryImpl
98+
let todoService: TodoServiceSpy
99+
let widgetSyncEventBus: WidgetSyncEventBusSpy
100+
}
101+
102+
private actor TodoServiceSpy: TodoService {
103+
private var shouldFail = false
104+
105+
func setShouldFail(_ shouldFail: Bool) {
106+
self.shouldFail = shouldFail
107+
}
108+
109+
func fetchTodos(_ query: TodoQuery, cursor: TodoCursorDTO?) async throws -> TodoPageResponse {
110+
throw TodoRepositoryImplTestsError.unexpectedCall
111+
}
112+
113+
func upsertTodo(request: TodoRequest) async throws {
114+
guard shouldFail == false else {
115+
throw TodoRepositoryImplTestsError.serviceFailed
116+
}
117+
}
118+
119+
func deleteTodo(todoId: String) async throws {
120+
guard shouldFail == false else {
121+
throw TodoRepositoryImplTestsError.serviceFailed
122+
}
123+
}
124+
125+
func undoDeleteTodo(todoId: String) async throws {
126+
guard shouldFail == false else {
127+
throw TodoRepositoryImplTestsError.serviceFailed
128+
}
129+
}
130+
131+
func fetchTodo(todoId: String) async throws -> TodoResponse {
132+
throw TodoRepositoryImplTestsError.unexpectedCall
133+
}
134+
135+
func fetchReferences(_ numbers: [Int]) async throws -> [Int: TodoReferenceResponse] {
136+
throw TodoRepositoryImplTestsError.unexpectedCall
137+
}
138+
}
139+
140+
private struct TodoCategoryServiceSpy: TodoCategoryService {
141+
func fetchPreferences() async throws -> [TodoCategoryPreferenceResponse] {
142+
[]
143+
}
144+
145+
func updatePreferences(_ preferences: [TodoCategoryPreferenceResponse]) async throws {
146+
throw TodoRepositoryImplTestsError.unexpectedCall
147+
}
148+
}
149+
150+
private final class WidgetSyncEventBusSpy: WidgetSyncEventBus {
151+
private(set) var events = [WidgetSyncEvent]()
152+
153+
func observe() -> AnyPublisher<WidgetSyncEvent, Never> {
154+
Empty().eraseToAnyPublisher()
155+
}
156+
157+
func publish(_ event: WidgetSyncEvent) {
158+
events.append(event)
159+
}
160+
}
161+
162+
private enum TodoRepositoryImplTestsError: Error, Equatable {
163+
case serviceFailed
164+
case unexpectedCall
165+
}

0 commit comments

Comments
 (0)