Skip to content

Commit a112678

Browse files
unam98alh0409
andauthored
test: Health/Scrap/Banner/UserStamp/ServerProfile 컨트롤러 @WebMvcTest 추가 (#227)
CourseController 파일럿에 이어 나머지 단순 컨트롤러들의 라우팅/@Valid 검증/ @userid 인증/예외→HTTP 상태코드 매핑을 슬라이스 테스트로 검증한다. Co-authored-by: 나미 <dnska6657@gmail.com>
1 parent 105bda3 commit a112678

5 files changed

Lines changed: 465 additions & 0 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.runnect.server.banner.controller;
2+
3+
import static org.mockito.Mockito.when;
4+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
5+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
6+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
7+
8+
import java.util.Collections;
9+
import java.util.List;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Test;
12+
import org.runnect.server.banner.dto.response.BannerResponse;
13+
import org.runnect.server.banner.dto.response.GetBannerResponseDto;
14+
import org.runnect.server.banner.service.BannerService;
15+
import org.runnect.server.config.jwt.JwtService;
16+
import org.runnect.server.config.slack.SlackApi;
17+
import org.springframework.beans.factory.annotation.Autowired;
18+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
19+
import org.springframework.boot.test.mock.mockito.MockBean;
20+
import org.springframework.test.web.servlet.MockMvc;
21+
22+
@WebMvcTest(BannerController.class)
23+
class BannerControllerTest {
24+
25+
@Autowired
26+
private MockMvc mockMvc;
27+
28+
@MockBean
29+
private BannerService bannerService;
30+
@MockBean
31+
private JwtService jwtService;
32+
@MockBean
33+
private SlackApi slackApi;
34+
35+
@Test
36+
@DisplayName("GET /api/banner - 정상 요청이면 200과 배너 목록을 반환한다")
37+
void 정상_조회() throws Exception {
38+
List<BannerResponse> banners = Collections.singletonList(BannerResponse.of(0, "image.png", "https://a.com"));
39+
when(bannerService.getBanners()).thenReturn(GetBannerResponseDto.of(banners));
40+
41+
mockMvc.perform(get("/api/banner"))
42+
.andExpect(status().isOk())
43+
.andExpect(jsonPath("$.data.banners[0].index").value(0))
44+
.andExpect(jsonPath("$.data.banners[0].imageUrl").value("image.png"));
45+
}
46+
47+
@Test
48+
@DisplayName("GET /api/banner - 배너가 없으면 200과 빈 목록을 반환한다")
49+
void 배너_없음() throws Exception {
50+
when(bannerService.getBanners()).thenReturn(GetBannerResponseDto.of(Collections.emptyList()));
51+
52+
mockMvc.perform(get("/api/banner"))
53+
.andExpect(status().isOk())
54+
.andExpect(jsonPath("$.data.banners").isEmpty());
55+
}
56+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.runnect.server.common.controller;
2+
3+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
4+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
5+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
6+
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
import org.runnect.server.config.jwt.JwtService;
10+
import org.runnect.server.config.slack.SlackApi;
11+
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
13+
import org.springframework.boot.test.mock.mockito.MockBean;
14+
import org.springframework.test.context.ActiveProfiles;
15+
import org.springframework.test.web.servlet.MockMvc;
16+
17+
@WebMvcTest(ServerProfileController.class)
18+
@ActiveProfiles("test")
19+
class ServerProfileControllerTest {
20+
21+
@Autowired
22+
private MockMvc mockMvc;
23+
24+
@MockBean
25+
private JwtService jwtService;
26+
@MockBean
27+
private SlackApi slackApi;
28+
29+
@Test
30+
@DisplayName("GET /profile - 활성 프로파일명을 그대로 반환한다")
31+
void 활성_프로파일_반환() throws Exception {
32+
mockMvc.perform(get("/profile"))
33+
.andExpect(status().isOk())
34+
.andExpect(content().string("test"));
35+
}
36+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package org.runnect.server.health.controller;
2+
3+
import static org.mockito.ArgumentMatchers.any;
4+
import static org.mockito.ArgumentMatchers.anyLong;
5+
import static org.mockito.ArgumentMatchers.eq;
6+
import static org.mockito.Mockito.when;
7+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
8+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
9+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
10+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
11+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
12+
13+
import com.fasterxml.jackson.databind.ObjectMapper;
14+
import java.util.Collections;
15+
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.DisplayName;
17+
import org.junit.jupiter.api.Nested;
18+
import org.junit.jupiter.api.Test;
19+
import org.mockito.BDDMockito;
20+
import org.runnect.server.common.constant.ErrorStatus;
21+
import org.runnect.server.common.constant.TokenStatus;
22+
import org.runnect.server.common.exception.ConflictException;
23+
import org.runnect.server.common.exception.PermissionDeniedException;
24+
import org.runnect.server.config.jwt.JwtService;
25+
import org.runnect.server.config.slack.SlackApi;
26+
import org.runnect.server.health.dto.request.HealthDataRequestDto;
27+
import org.runnect.server.health.dto.response.CreateHealthDataResponseDto;
28+
import org.runnect.server.health.dto.response.GetHealthDataResponseDto;
29+
import org.runnect.server.health.dto.response.GetHealthSummaryResponseDto;
30+
import org.runnect.server.health.service.HealthService;
31+
import org.springframework.beans.factory.annotation.Autowired;
32+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
33+
import org.springframework.boot.test.mock.mockito.MockBean;
34+
import org.springframework.test.web.servlet.MockMvc;
35+
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
36+
37+
@WebMvcTest(HealthController.class)
38+
class HealthControllerTest {
39+
40+
@Autowired
41+
private MockMvc mockMvc;
42+
43+
@Autowired
44+
private ObjectMapper objectMapper;
45+
46+
@MockBean
47+
private HealthService healthService;
48+
@MockBean
49+
private JwtService jwtService;
50+
@MockBean
51+
private SlackApi slackApi;
52+
53+
@BeforeEach
54+
void setUpAuth() {
55+
BDDMockito.lenient().when(jwtService.verifyToken("valid")).thenReturn(TokenStatus.TOKEN_VALID);
56+
BDDMockito.lenient().when(jwtService.getJwtContents("valid")).thenReturn("1");
57+
}
58+
59+
private <B extends MockHttpServletRequestBuilder> B withAuth(B builder) {
60+
builder.header("accessToken", "valid").header("refreshToken", "valid");
61+
return builder;
62+
}
63+
64+
private HealthDataRequestDto validRequest() {
65+
return new HealthDataRequestDto(150.0, 180.0, 100.0, 320.0, 60, 120, 90, 30, 10, 190.0, Collections.emptyList());
66+
}
67+
68+
@Nested
69+
@DisplayName("POST /api/record/{recordId}/health")
70+
class CreateHealthData {
71+
72+
@Test
73+
@DisplayName("정상 요청이면 201과 생성된 건강 데이터 id를 반환한다")
74+
void 정상_생성() throws Exception {
75+
when(healthService.createHealthData(eq(1L), eq(10L), any()))
76+
.thenReturn(CreateHealthDataResponseDto.of(100L));
77+
78+
mockMvc.perform(withAuth(post("/api/record/10/health"))
79+
.contentType("application/json")
80+
.content(objectMapper.writeValueAsString(validRequest())))
81+
.andExpect(status().isCreated())
82+
.andExpect(jsonPath("$.data.healthDataId").value(100));
83+
}
84+
85+
@Test
86+
@DisplayName("필수값(avgHeartRate)이 없으면 400을 반환한다")
87+
void 필수값_없음() throws Exception {
88+
mockMvc.perform(withAuth(post("/api/record/10/health"))
89+
.contentType("application/json")
90+
.content("{\"calories\":320.0,\"zone1Seconds\":1,\"zone2Seconds\":1,\"zone3Seconds\":1,\"zone4Seconds\":1,\"zone5Seconds\":1}"))
91+
.andExpect(status().isBadRequest());
92+
93+
BDDMockito.verifyNoInteractions(healthService);
94+
}
95+
96+
@Test
97+
@DisplayName("다른 유저의 레코드면 403을 반환한다")
98+
void 소유권_없음() throws Exception {
99+
when(healthService.createHealthData(eq(1L), eq(10L), any()))
100+
.thenThrow(new PermissionDeniedException(
101+
ErrorStatus.PERMISSION_DENIED_HEALTH_DATA_EXCEPTION,
102+
ErrorStatus.PERMISSION_DENIED_HEALTH_DATA_EXCEPTION.getMessage()));
103+
104+
mockMvc.perform(withAuth(post("/api/record/10/health"))
105+
.contentType("application/json")
106+
.content(objectMapper.writeValueAsString(validRequest())))
107+
.andExpect(status().isForbidden());
108+
}
109+
110+
@Test
111+
@DisplayName("이미 건강 데이터가 있으면 409를 반환한다")
112+
void 이미_존재() throws Exception {
113+
when(healthService.createHealthData(eq(1L), eq(10L), any()))
114+
.thenThrow(new ConflictException(
115+
ErrorStatus.ALREADY_EXIST_HEALTH_DATA_EXCEPTION,
116+
ErrorStatus.ALREADY_EXIST_HEALTH_DATA_EXCEPTION.getMessage()));
117+
118+
mockMvc.perform(withAuth(post("/api/record/10/health"))
119+
.contentType("application/json")
120+
.content(objectMapper.writeValueAsString(validRequest())))
121+
.andExpect(status().isConflict());
122+
}
123+
}
124+
125+
@Nested
126+
@DisplayName("GET /api/record/{recordId}/health")
127+
class GetHealthData {
128+
129+
@Test
130+
@DisplayName("정상 요청이면 200과 건강 데이터를 반환한다")
131+
void 정상_조회() throws Exception {
132+
GetHealthDataResponseDto.ZoneResponse zones =
133+
GetHealthDataResponseDto.ZoneResponse.of(60, 120, 90, 30, 10);
134+
GetHealthDataResponseDto response = GetHealthDataResponseDto.of(
135+
GetHealthDataResponseDto.HealthDataDetailResponse.of(
136+
100L, 10L, 150.0, 180.0, 100.0, 320.0, zones, 190.0, Collections.emptyList()));
137+
when(healthService.getHealthData(anyLong(), eq(10L))).thenReturn(response);
138+
139+
mockMvc.perform(withAuth(get("/api/record/10/health")))
140+
.andExpect(status().isOk())
141+
.andExpect(jsonPath("$.data.healthData.id").value(100));
142+
}
143+
}
144+
145+
@Nested
146+
@DisplayName("GET /api/health/summary")
147+
class GetHealthSummary {
148+
149+
@Test
150+
@DisplayName("정상 요청이면 200과 요약 데이터를 반환한다")
151+
void 정상_조회() throws Exception {
152+
GetHealthSummaryResponseDto response = GetHealthSummaryResponseDto.of(
153+
GetHealthSummaryResponseDto.HealthSummaryResponse.of(
154+
10L, 5L, 150.0, 300.0, 1500.0,
155+
GetHealthDataResponseDto.ZoneResponse.of(60, 120, 90, 30, 10)));
156+
when(healthService.getHealthSummary(1L, "2026-01-01", "2026-01-31")).thenReturn(response);
157+
158+
mockMvc.perform(withAuth(get("/api/health/summary")
159+
.param("startDate", "2026-01-01")
160+
.param("endDate", "2026-01-31")))
161+
.andExpect(status().isOk())
162+
.andExpect(jsonPath("$.data.summary.totalRecords").value(10));
163+
}
164+
165+
@Test
166+
@DisplayName("필수 파라미터가 없으면 400을 반환한다")
167+
void 파라미터_없음() throws Exception {
168+
mockMvc.perform(withAuth(get("/api/health/summary").param("startDate", "2026-01-01")))
169+
.andExpect(status().isBadRequest());
170+
171+
BDDMockito.verifyNoInteractions(healthService);
172+
}
173+
}
174+
175+
@Nested
176+
@DisplayName("DELETE /api/record/{recordId}/health")
177+
class DeleteHealthData {
178+
179+
@Test
180+
@DisplayName("정상 요청이면 200을 반환한다")
181+
void 정상_삭제() throws Exception {
182+
mockMvc.perform(withAuth(delete("/api/record/10/health")))
183+
.andExpect(status().isOk())
184+
.andExpect(jsonPath("$.success").value(true));
185+
}
186+
}
187+
}

0 commit comments

Comments
 (0)