Skip to content

Commit ab19eb0

Browse files
committed
Merge branch 'feat/jacoco' into develop
2 parents 6fc5fef + bca4538 commit ab19eb0

31 files changed

+6455
-107
lines changed

.github/workflows/run-tests.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,10 @@ jobs:
2121
distribution: 'temurin'
2222
cache: gradle
2323

24-
- name: Run memory-api tests
25-
run: ./gradlew :memory-api:test
24+
- name: Run memory-api tests with coverage
25+
run: ./gradlew :memory-api:test :memory-api:jacocoTestReport
26+
27+
- name: Upload coverage reports to Codecov
28+
uses: codecov/codecov-action@v3
29+
with:
30+
file: ./memory-api/build/reports/jacoco/test/jacocoTestReport.xml

build.gradle

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
id 'java'
33
id 'org.springframework.boot' version '3.5.3'
44
id 'io.spring.dependency-management' version '1.1.7'
5+
id 'jacoco'
56
}
67

78
repositories {
@@ -11,21 +12,56 @@ repositories {
1112
bootJar {enabled = false}
1213
jar {enabled = true}
1314

15+
tasks.register('jacocoRootReport', JacocoReport) {
16+
description = 'Generates an aggregate report from all subprojects'
17+
group = 'reporting'
18+
19+
subprojects { subproject ->
20+
subproject.plugins.withType(JacocoPlugin) {
21+
dependsOn subproject.test
22+
23+
sourceDirectories.from += subproject.sourceSets.main.allSource.srcDirs
24+
classDirectories.from += subproject.sourceSets.main.output
25+
executionData.from += subproject.jacocoTestReport.executionData
26+
}
27+
}
28+
29+
reports {
30+
html.required = true
31+
xml.required = true
32+
csv.required = false
33+
}
34+
35+
doFirst {
36+
executionData = files(executionData.findAll { it.exists() })
37+
}
38+
}
39+
1440
allprojects {
1541
group = 'com'
1642
version = '0.0.1-SNAPSHOT'
17-
sourceCompatibility = '17'
43+
}
44+
45+
java {
46+
sourceCompatibility = JavaVersion.VERSION_17
47+
targetCompatibility = JavaVersion.VERSION_17
1848
}
1949

2050
subprojects {
2151
apply plugin: 'java'
2252
apply plugin: 'java-library'
2353
apply plugin: 'org.springframework.boot'
2454
apply plugin: 'io.spring.dependency-management'
55+
apply plugin: 'jacoco'
2556

2657
repositories {
2758
mavenCentral()
2859
}
60+
61+
java {
62+
sourceCompatibility = JavaVersion.VERSION_17
63+
targetCompatibility = JavaVersion.VERSION_17
64+
}
2965

3066
configurations {
3167
compileOnly {
@@ -47,4 +83,48 @@ subprojects {
4783
testCompileOnly 'org.projectlombok:lombok'
4884
testAnnotationProcessor 'org.projectlombok:lombok'
4985
}
86+
87+
jacoco {
88+
toolVersion = "0.8.10"
89+
}
90+
91+
jacocoTestReport {
92+
dependsOn test, compileJava, processResources
93+
reports {
94+
xml.required = true
95+
html.required = true
96+
csv.required = false
97+
}
98+
99+
afterEvaluate {
100+
classDirectories.setFrom(files(classDirectories.files.collect {
101+
fileTree(dir: it, exclude: [
102+
'**/config/**',
103+
'**/dto/**',
104+
'**/entity/**',
105+
'**/domain/**',
106+
'**/*Application*',
107+
'**/*Config*',
108+
'**/*Exception*'
109+
])
110+
}))
111+
}
112+
}
113+
114+
jacocoTestCoverageVerification {
115+
dependsOn jacocoTestReport
116+
117+
violationRules {
118+
rule {
119+
limit {
120+
minimum = 0.70
121+
}
122+
}
123+
}
124+
}
125+
126+
test {
127+
useJUnitPlatform()
128+
finalizedBy jacocoTestReport
129+
}
50130
}

memory-api/src/main/java/com/memory/controller/game/GameSessionController.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class GameSessionController {
2828
response = GameSessionResponse.class
2929
)
3030
@Auth
31-
@PostMapping("api/v1/game/sessions")
31+
@PostMapping("/api/v1/game/sessions")
3232
public ServerResponse<GameSessionResponse> createGameSession(
3333
@Parameter(hidden = true) @MemberId Long memberId,
3434
@RequestBody @Valid GameSessionRequest.Create request) {
@@ -41,7 +41,7 @@ public ServerResponse<GameSessionResponse> createGameSession(
4141
response = GameSessionResponse.class
4242
)
4343
@Auth
44-
@GetMapping("api/v1/game/sessions/{sessionId}")
44+
@GetMapping("/api/v1/game/sessions/{sessionId}")
4545
public ServerResponse<GameSessionResponse> findGameSessionById(
4646
@Parameter(hidden = true) @MemberId Long memberId,
4747
@PathVariable Long sessionId) {
@@ -54,7 +54,7 @@ public ServerResponse<GameSessionResponse> findGameSessionById(
5454
response = GameSessionResponse.class
5555
)
5656
@Auth
57-
@GetMapping("api/v1/game/sessions/current")
57+
@GetMapping("/api/v1/game/sessions/current")
5858
public ServerResponse<GameSessionResponse> findProgressGameSession(
5959
@Parameter(hidden = true) @MemberId Long memberId) {
6060
return ServerResponse.success(gameSessionService.findProgressGameSession(memberId));
@@ -66,7 +66,7 @@ public ServerResponse<GameSessionResponse> findProgressGameSession(
6666
response = GameSessionResponse.class
6767
)
6868
@Auth
69-
@GetMapping("api/v1/game/sessions")
69+
@GetMapping("/api/v1/game/sessions")
7070
public ServerResponse<List<GameSessionResponse>> findGameSessionsByMember(
7171
@Parameter(hidden = true) @MemberId Long memberId,
7272
GameSessionRequest.GetList request) {
@@ -78,7 +78,7 @@ public ServerResponse<List<GameSessionResponse>> findGameSessionsByMember(
7878
description = "진행중인 게임 세션을 포기합니다."
7979
)
8080
@Auth
81-
@PatchMapping("api/v1/game/sessions/{sessionId}/give-up")
81+
@PatchMapping("/api/v1/game/sessions/{sessionId}/give-up")
8282
public ServerResponse<String> giveUpGameSession(
8383
@Parameter(hidden = true) @MemberId Long memberId,
8484
@PathVariable Long sessionId) {
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package com.memory.controller.calendar;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
5+
import com.memory.config.jwt.JwtTokenProvider;
6+
import com.memory.domain.calendar.CalendarEventType;
7+
import com.memory.domain.calendar.PersonalEvent;
8+
import com.memory.domain.calendar.repository.PersonalEventRepository;
9+
import com.memory.domain.member.Member;
10+
import com.memory.domain.member.MemberType;
11+
import com.memory.domain.member.repository.MemberRepository;
12+
import com.memory.dto.calendar.CalendarEventRequest;
13+
import org.junit.jupiter.api.*;
14+
import org.springframework.beans.factory.annotation.Autowired;
15+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
16+
import org.springframework.boot.test.context.SpringBootTest;
17+
import org.springframework.http.MediaType;
18+
import org.springframework.test.context.ActiveProfiles;
19+
import org.springframework.test.web.servlet.MockMvc;
20+
import org.springframework.transaction.annotation.Transactional;
21+
22+
import java.time.LocalDateTime;
23+
24+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
25+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
26+
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
27+
28+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
29+
@AutoConfigureMockMvc
30+
@ActiveProfiles("test")
31+
@Transactional
32+
class CalendarEventControllerIntegrationTest {
33+
34+
@Autowired
35+
private MockMvc mockMvc;
36+
37+
private ObjectMapper objectMapper;
38+
39+
@Autowired
40+
private JwtTokenProvider jwtTokenProvider;
41+
42+
@Autowired
43+
private MemberRepository memberRepository;
44+
45+
@Autowired
46+
private PersonalEventRepository personalEventRepository;
47+
48+
private Member testMember;
49+
private PersonalEvent testPersonalEvent;
50+
private String validToken;
51+
52+
@BeforeEach
53+
void setUp() {
54+
// ObjectMapper JavaTimeModule 등록
55+
objectMapper = new ObjectMapper();
56+
objectMapper.registerModule(new JavaTimeModule());
57+
58+
// Member 생성 및 저장
59+
String uniqueEmail = "test" + System.currentTimeMillis() + "@example.com";
60+
testMember = new Member("Test User", "testuser", uniqueEmail, "encodedPassword", MemberType.MEMBER);
61+
testMember = memberRepository.save(testMember);
62+
63+
// JWT 토큰 생성
64+
validToken = "Bearer " + jwtTokenProvider.createAccessToken(testMember.getEmail());
65+
66+
// PersonalEvent 생성 및 저장
67+
testPersonalEvent = PersonalEvent.create(
68+
"Test Personal Event",
69+
"Test Description",
70+
LocalDateTime.now().plusDays(1),
71+
LocalDateTime.now().plusDays(1).plusHours(2),
72+
"Test Location",
73+
testMember
74+
);
75+
testPersonalEvent = personalEventRepository.save(testPersonalEvent);
76+
}
77+
78+
@Test
79+
@DisplayName("개인 일정 생성 통합 테스트 - 성공")
80+
void createPersonalEventIntegrationSuccess() throws Exception {
81+
// Given
82+
CalendarEventRequest.Create request = new CalendarEventRequest.Create(
83+
"New Personal Event",
84+
"New Description",
85+
LocalDateTime.now().plusDays(2),
86+
LocalDateTime.now().plusDays(2).plusHours(3),
87+
"New Location",
88+
CalendarEventType.PERSONAL,
89+
false
90+
);
91+
92+
// When & Then
93+
mockMvc.perform(post("/api/v1/calendar/events")
94+
.header("Authorization", validToken)
95+
.contentType(MediaType.APPLICATION_JSON)
96+
.content(objectMapper.writeValueAsString(request)))
97+
.andDo(print())
98+
.andExpect(status().isOk())
99+
.andExpect(jsonPath("$.statusCode").value(200))
100+
.andExpect(jsonPath("$.message").value("OK"))
101+
.andExpect(jsonPath("$.data").exists())
102+
.andExpect(jsonPath("$.data.title").value("New Personal Event"))
103+
.andExpect(jsonPath("$.data.description").value("New Description"));
104+
}
105+
106+
@Test
107+
@DisplayName("일정 생성 실패 - 인증 토큰 없음")
108+
void createCalendarEventFailNoAuth() throws Exception {
109+
// Given
110+
CalendarEventRequest.Create request = new CalendarEventRequest.Create(
111+
"New Event", "Description", LocalDateTime.now().plusDays(1),
112+
LocalDateTime.now().plusDays(1).plusHours(2), "Location",
113+
CalendarEventType.PERSONAL, false
114+
);
115+
116+
// When & Then
117+
mockMvc.perform(post("/api/v1/calendar/events")
118+
.contentType(MediaType.APPLICATION_JSON)
119+
.content(objectMapper.writeValueAsString(request)))
120+
.andExpect(status().isUnauthorized());
121+
}
122+
123+
@Test
124+
@DisplayName("일정 생성 실패 - 잘못된 요청 데이터")
125+
void createCalendarEventFailInvalidRequest() throws Exception {
126+
// When & Then
127+
mockMvc.perform(post("/api/v1/calendar/events")
128+
.header("Authorization", validToken)
129+
.contentType(MediaType.APPLICATION_JSON)
130+
.content("{}"))
131+
.andExpect(status().isBadRequest());
132+
}
133+
134+
@Test
135+
@DisplayName("일정 수정 통합 테스트")
136+
void updateCalendarEventIntegrationTest() throws Exception {
137+
// Given
138+
CalendarEventRequest.Update request = new CalendarEventRequest.Update(
139+
"Updated Personal Event",
140+
"Updated Description",
141+
LocalDateTime.now().plusDays(3),
142+
LocalDateTime.now().plusDays(3).plusHours(2),
143+
"Updated Location",
144+
CalendarEventType.PERSONAL,
145+
false
146+
);
147+
148+
// When & Then
149+
mockMvc.perform(put("/api/v1/calendar/events/{eventId}", testPersonalEvent.getId())
150+
.header("Authorization", validToken)
151+
.contentType(MediaType.APPLICATION_JSON)
152+
.content(objectMapper.writeValueAsString(request)))
153+
.andDo(print())
154+
.andExpect(status().isOk())
155+
.andExpect(jsonPath("$.statusCode").value(200))
156+
.andExpect(jsonPath("$.data.title").value("Updated Personal Event"));
157+
}
158+
159+
@Test
160+
@DisplayName("특정 기간 내 일정 조회 통합 테스트")
161+
void getCalendarEventsByDateRangeIntegrationTest() throws Exception {
162+
// When & Then
163+
mockMvc.perform(get("/api/v1/calendar/events")
164+
.header("Authorization", validToken)
165+
.param("startDate", "2025-08-01")
166+
.param("endDate", "2025-12-31"))
167+
.andDo(print())
168+
.andExpect(status().isOk())
169+
.andExpect(jsonPath("$.statusCode").value(200))
170+
.andExpect(jsonPath("$.data").isArray());
171+
}
172+
173+
@Test
174+
@DisplayName("D-day 조회 통합 테스트")
175+
void getCalendarEventsWithDdayIntegrationTest() throws Exception {
176+
// When & Then
177+
mockMvc.perform(get("/api/v1/calendar/events/dday")
178+
.header("Authorization", validToken))
179+
.andDo(print())
180+
.andExpect(status().isOk())
181+
.andExpect(jsonPath("$.statusCode").value(200))
182+
.andExpect(jsonPath("$.data").isArray());
183+
}
184+
}

0 commit comments

Comments
 (0)