Skip to content

Commit f2c39b9

Browse files
authored
Merge pull request #317 from DevKor-github/feature/coverage-95
[codex] Raise backend coverage to 95 percent
2 parents 210c66d + c05b331 commit f2c39b9

74 files changed

Lines changed: 7226 additions & 484 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,21 @@ jobs:
6161
6262
6363
64-
# 4. Gradle 빌드 & JUnit 테스트 실행
64+
# 4. Gradle 검증 및 커버리지 검사 실행
6565
- name: Run Tests with Gradle
6666
env:
6767
SPRING_PROFILES_ACTIVE: test
6868
run: |
6969
cd ontime-back
70-
./gradlew test
70+
./gradlew check
71+
72+
- name: Upload JaCoCo Coverage Report
73+
if: always()
74+
uses: actions/upload-artifact@v4
75+
with:
76+
name: jacoco-coverage-report
77+
path: ontime-back/build/reports/jacoco/test/
78+
if-no-files-found: warn
7179

7280
- name: Verify Flyway Migrations
7381
run: |

docs/schedule-start-api.md

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# Schedule Start API
2+
3+
Frontend integration guide for server-authoritative preparation start state.
4+
5+
## Summary
6+
7+
The backend now uses `startedAt` as the source of truth for whether preparation has started. Flutter should stop treating client-side `isStarted` as authoritative.
8+
9+
Preparation becomes frozen when the user starts a schedule:
10+
11+
- Default preparation is a mutable template.
12+
- Started schedule preparation is a schedule-specific snapshot.
13+
- After `startedAt` is set, schedule preparation reads come from the frozen snapshot, not from the user's default preparation.
14+
15+
## Authentication
16+
17+
All endpoints require the current OnTime access token.
18+
19+
```http
20+
Authorization: Bearer {accessToken}
21+
Content-Type: application/json
22+
```
23+
24+
## Schedule Response Field
25+
26+
Schedule responses now include nullable `startedAt`.
27+
28+
```json
29+
{
30+
"scheduleId": "3fa85f64-5717-4562-b3fc-2c963f66afe5",
31+
"place": {
32+
"placeId": "70d460da-6a82-4c57-a285-567cdeda5670",
33+
"placeName": "Home"
34+
},
35+
"scheduleName": "Party",
36+
"moveTime": 20,
37+
"scheduleTime": "2026-05-13T19:30:00",
38+
"scheduleSpareTime": 20,
39+
"scheduleNote": "Write a message.",
40+
"latenessTime": -1,
41+
"doneStatus": "NOT_ENDED",
42+
"startedAt": "2026-05-13T10:15:30Z",
43+
"finishedAt": null
44+
}
45+
```
46+
47+
Fields:
48+
49+
| Field | Type | Notes |
50+
| --- | --- | --- |
51+
| `startedAt` | ISO-8601 UTC datetime or `null` | `null` means preparation has not explicitly started. Non-null means the schedule is locked for editing. |
52+
| `finishedAt` | ISO-8601 UTC datetime or `null` | Non-null means the schedule was explicitly finished by the finish endpoint. |
53+
| `doneStatus` | enum | `NOT_ENDED`, `NORMAL`, `LATE`, or `ABNORMAL`. Finished schedules cannot be edited or deleted. |
54+
| `latenessTime` | integer or `null` | Completion result. `-1` is legacy/unended data; new finish calls use `0` for normal or positive minutes for late. |
55+
56+
Frontend rule:
57+
58+
```text
59+
canEditSchedule = doneStatus == "NOT_ENDED" && startedAt == null
60+
canDeleteSchedule = doneStatus == "NOT_ENDED"
61+
```
62+
63+
## Start Preparation
64+
65+
Call this endpoint when the user taps "Start preparation".
66+
67+
```http
68+
POST /schedules/{scheduleId}/start
69+
```
70+
71+
Request body: none.
72+
73+
Behavior:
74+
75+
- If the schedule has not started, backend sets `startedAt` to server time.
76+
- If the schedule still uses default preparation, backend copies the current default preparation into schedule-specific preparation rows.
77+
- If the schedule already started, backend returns success without changing `startedAt` or replacing the frozen preparation snapshot.
78+
- If the schedule is finished, backend returns `409 SCHEDULE_ALREADY_FINISHED`.
79+
80+
Success response:
81+
82+
```json
83+
{
84+
"status": "success",
85+
"code": 200,
86+
"message": "OK",
87+
"data": {
88+
"schedule": {
89+
"scheduleId": "3fa85f64-5717-4562-b3fc-2c963f66afe5",
90+
"place": {
91+
"placeId": "70d460da-6a82-4c57-a285-567cdeda5670",
92+
"placeName": "Home"
93+
},
94+
"scheduleName": "Party",
95+
"moveTime": 20,
96+
"scheduleTime": "2026-05-13T19:30:00",
97+
"scheduleSpareTime": 20,
98+
"scheduleNote": "Write a message.",
99+
"latenessTime": -1,
100+
"doneStatus": "NOT_ENDED",
101+
"startedAt": "2026-05-13T10:15:30Z",
102+
"finishedAt": null
103+
},
104+
"preparations": [
105+
{
106+
"preparationId": "123e4567-e89b-12d3-a456-426614174011",
107+
"preparationName": "Wash up",
108+
"preparationTime": 10,
109+
"nextPreparationId": "123e4567-e89b-12d3-a456-426614174012"
110+
},
111+
{
112+
"preparationId": "123e4567-e89b-12d3-a456-426614174012",
113+
"preparationName": "Get dressed",
114+
"preparationTime": 15,
115+
"nextPreparationId": null
116+
}
117+
]
118+
}
119+
}
120+
```
121+
122+
Frontend behavior:
123+
124+
- After success, update local schedule state from `data.schedule`.
125+
- Use `data.preparations` as the running preparation steps.
126+
- Hide or disable schedule edit actions because `startedAt != null`.
127+
- It is safe to retry this request; the endpoint is idempotent.
128+
129+
## Update Schedule
130+
131+
Existing endpoint:
132+
133+
```http
134+
PUT /schedules/{scheduleId}
135+
```
136+
137+
New server-side guard:
138+
139+
- Allowed only when `doneStatus == "NOT_ENDED"` and `startedAt == null`.
140+
- Backend ignores incoming `isStarted`; do not send or depend on it for locking.
141+
142+
Started schedule error:
143+
144+
```json
145+
{
146+
"status": "error",
147+
"code": "SCHEDULE_ALREADY_STARTED",
148+
"message": "Started schedules cannot be edited.",
149+
"data": null
150+
}
151+
```
152+
153+
Finished schedule error:
154+
155+
```json
156+
{
157+
"status": "error",
158+
"code": "SCHEDULE_ALREADY_FINISHED",
159+
"message": "Finished schedules cannot be edited.",
160+
"data": null
161+
}
162+
```
163+
164+
## Update Schedule-Specific Preparation
165+
166+
Existing endpoints:
167+
168+
```http
169+
POST /schedules/{scheduleId}/preparations
170+
PUT /schedules/{scheduleId}/preparations
171+
```
172+
173+
New server-side guard:
174+
175+
- Allowed only when `doneStatus == "NOT_ENDED"` and `startedAt == null`.
176+
- Returns `409 SCHEDULE_ALREADY_STARTED` after preparation has started.
177+
- Returns `409 SCHEDULE_ALREADY_FINISHED` after the schedule is finished.
178+
179+
Reason:
180+
181+
- Editing steps or durations after start would invalidate the active preparation flow.
182+
183+
## Delete Schedule
184+
185+
Existing endpoint:
186+
187+
```http
188+
DELETE /schedules/{scheduleId}
189+
```
190+
191+
Rule:
192+
193+
- Allowed when `doneStatus == "NOT_ENDED"`.
194+
- Started but unfinished schedules can be deleted.
195+
- Finished schedules cannot be deleted.
196+
197+
Finished schedule error:
198+
199+
```json
200+
{
201+
"status": "error",
202+
"code": "SCHEDULE_ALREADY_FINISHED",
203+
"message": "Finished schedules cannot be edited.",
204+
"data": null
205+
}
206+
```
207+
208+
## Default Preparation Updates
209+
210+
Existing user-default preparation update remains allowed.
211+
212+
```http
213+
PUT /preparations
214+
```
215+
216+
Frontend behavior:
217+
218+
- Users may edit default preparation in settings even if they have already started a schedule.
219+
- This updates only the default template.
220+
- It does not change any started schedule's frozen preparation snapshot.
221+
- Future or unstarted schedules that still use default preparation may resolve the updated default template.
222+
223+
## Finish Preparation
224+
225+
Existing endpoint:
226+
227+
```http
228+
PUT /schedules/{scheduleId}/finish
229+
```
230+
231+
New server-side guard:
232+
233+
- Allowed only after explicit start, when `startedAt != null`.
234+
- Unstarted missed schedules remain `NOT_ENDED` and do not count toward punctuality score.
235+
- Finished schedules still return `409 SCHEDULE_ALREADY_FINISHED`.
236+
237+
Unstarted schedule error:
238+
239+
```json
240+
{
241+
"status": "error",
242+
"code": "SCHEDULE_NOT_STARTED",
243+
"message": "Schedules must be started before they can be finished.",
244+
"data": null
245+
}
246+
```
247+
248+
Punctuality scoring rule:
249+
250+
```text
251+
includedInPunctualityScore =
252+
startedAt != null
253+
&& finishedAt != null
254+
&& doneStatus in (NORMAL, LATE)
255+
```
256+
257+
So a schedule with:
258+
259+
```text
260+
doneStatus == NOT_ENDED
261+
startedAt == null
262+
scheduleTime < now
263+
```
264+
265+
is a missed/unstarted schedule. It can be deleted, but it is not auto-finished and does not affect punctuality score.
266+
267+
`ABNORMAL` is also excluded from punctuality score. It is reserved for abnormal completion states and should not improve or worsen the score.
268+
269+
## Alarm Window Response
270+
271+
`GET /schedules/alarm-window` also includes `startedAt` and `finishedAt` for each schedule.
272+
273+
```json
274+
{
275+
"scheduleId": "3fa85f64-5717-4562-b3fc-2c963f66afe5",
276+
"scheduleName": "Morning meeting",
277+
"scheduleTime": "2026-05-13T09:30:00",
278+
"moveTime": 20,
279+
"scheduleSpareTime": 10,
280+
"doneStatus": "NOT_ENDED",
281+
"startedAt": "2026-05-13T08:15:30Z",
282+
"finishedAt": null,
283+
"preparationStartTime": "2026-05-13T08:40:00",
284+
"defaultAlarmTime": "2026-05-13T08:30:00",
285+
"preparations": []
286+
}
287+
```
288+
289+
## Migration Notes For Flutter
290+
291+
Recommended client changes:
292+
293+
- Read `startedAt` from schedule responses.
294+
- Read `finishedAt` when displaying explicit completion state.
295+
- Treat `startedAt != null` as "preparation has started".
296+
- Stop treating `isStarted` as authoritative.
297+
- Call `POST /schedules/{id}/start` only when the user explicitly taps "Start preparation".
298+
- On start success, replace local running preparation state with `data.preparations`.
299+
- Hide or disable schedule/preparation edit actions when `startedAt != null`.
300+
- Hide or disable edit/delete actions when `doneStatus != "NOT_ENDED"`, except delete is still allowed for started schedules if `doneStatus == "NOT_ENDED"`.
301+
- Do not call finish for schedules that never successfully started.

ontime-back/build.gradle

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id 'java'
3+
id 'jacoco'
34
id 'org.springframework.boot' version '3.3.4'
45
id 'io.spring.dependency-management' version '1.1.6'
56
id 'org.flywaydb.flyway' version '9.22.1'
@@ -70,4 +71,30 @@ dependencies {
7071

7172
tasks.named('test') {
7273
useJUnitPlatform()
74+
finalizedBy tasks.named('jacocoTestReport')
75+
}
76+
77+
tasks.named('jacocoTestReport') {
78+
dependsOn tasks.named('test')
79+
80+
reports {
81+
xml.required = true
82+
html.required = true
83+
}
84+
}
85+
86+
tasks.named('jacocoTestCoverageVerification') {
87+
dependsOn tasks.named('jacocoTestReport')
88+
89+
violationRules {
90+
rule {
91+
limit {
92+
minimum = 0.95
93+
}
94+
}
95+
}
96+
}
97+
98+
tasks.named('check') {
99+
dependsOn tasks.named('jacocoTestCoverageVerification')
73100
}

ontime-back/src/main/java/devkor/ontime_back/config/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
7272
.headers(headers -> headers
7373
.frameOptions(frameOptions -> frameOptions.disable()))
7474
.authorizeHttpRequests(auth -> auth
75-
.requestMatchers("/", "/account-deletion", "/privacy-policy", "/css/**", "/images/**", "/js/**", "/favicon.ico", "/h2-console/**").permitAll()
75+
.requestMatchers("/", "/account-deletion", "/account-deletion/**", "/privacy-policy", "/privacy-policy/**", "/css/**", "/images/**", "/js/**", "/favicon.ico", "/h2-console/**").permitAll()
7676
.requestMatchers("/health", "/actuator/health/**", "/oauth2/sign-up", "oauth2/success", "login/success", "/oauth2/google/login", "/oauth2/kakao/login", "/oauth2/apple/login", "/sign-up", "/*/additional-info").permitAll()
7777
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**", "/webjars/**", "/swagger-ui.html").permitAll()
7878
.requestMatchers("/error").permitAll()

0 commit comments

Comments
 (0)