Skip to content

Commit e4d2012

Browse files
author
EpicFn
committed
refactor : 테스트 코드 수정
1 parent 7b763c2 commit e4d2012

File tree

3 files changed

+209
-71
lines changed

3 files changed

+209
-71
lines changed

src/main/java/org/tuna/zoopzoop/backend/domain/dashboard/controller/ApiV1DashboardController.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55
import lombok.RequiredArgsConstructor;
66
import org.springframework.http.HttpStatus;
77
import org.springframework.http.ResponseEntity;
8+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
89
import org.springframework.web.bind.annotation.*;
910
import org.tuna.zoopzoop.backend.domain.dashboard.dto.BodyForReactFlow;
1011
import org.tuna.zoopzoop.backend.domain.dashboard.entity.Graph;
1112
import org.tuna.zoopzoop.backend.domain.dashboard.service.DashboardService;
1213
import org.tuna.zoopzoop.backend.domain.dashboard.service.GraphService;
14+
import org.tuna.zoopzoop.backend.domain.member.entity.Member;
1315
import org.tuna.zoopzoop.backend.global.rsData.RsData;
16+
import org.tuna.zoopzoop.backend.global.security.jwt.CustomUserDetails;
17+
18+
import java.nio.file.AccessDeniedException;
1419

1520
@RestController
1621
@RequiredArgsConstructor
@@ -43,14 +48,19 @@ public ResponseEntity<RsData<Void>> updateGraph(
4348
}
4449

4550
/**
46-
* LiveBlocks를 위한 React-flow 데이터 조회 API
51+
* React-flow 데이터 조회 API
4752
* @param dashboardId React-flow 데이터의 dashboard 식별 id
4853
*/
4954
@GetMapping("/{dashboardId}/graph")
5055
@Operation(summary = "React-flow 데이터 조회")
5156
public ResponseEntity<RsData<BodyForReactFlow>> getGraph(
52-
@PathVariable Integer dashboardId
53-
) {
57+
@PathVariable Integer dashboardId,
58+
@AuthenticationPrincipal CustomUserDetails userDetails
59+
) throws AccessDeniedException {
60+
// TODO : 권한 체크 로직 추가
61+
Member member = userDetails.getMember();
62+
dashboardService.verifyAccessPermission(member, dashboardId);
63+
5464
Graph graph = dashboardService.getGraphByDashboardId(dashboardId);
5565
return ResponseEntity
5666
.status(HttpStatus.OK)
@@ -59,5 +69,6 @@ public ResponseEntity<RsData<BodyForReactFlow>> getGraph(
5969
"ID: " + dashboardId + " 의 React-flow 데이터를 조회했습니다.",
6070
BodyForReactFlow.from(graph)
6171
));
72+
6273
}
6374
}

src/main/java/org/tuna/zoopzoop/backend/domain/dashboard/service/DashboardService.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@
1010
import org.tuna.zoopzoop.backend.domain.dashboard.entity.Graph;
1111
import org.tuna.zoopzoop.backend.domain.dashboard.entity.Node;
1212
import org.tuna.zoopzoop.backend.domain.dashboard.repository.DashboardRepository;
13+
import org.tuna.zoopzoop.backend.domain.member.entity.Member;
14+
import org.tuna.zoopzoop.backend.domain.space.membership.service.MembershipService;
1315

16+
import java.nio.file.AccessDeniedException;
1417
import java.util.List;
1518

1619
@Service
1720
@RequiredArgsConstructor
1821
@Transactional
1922
public class DashboardService {
2023
private final DashboardRepository dashboardRepository;
24+
private final MembershipService membershipService;
2125
/**
2226
* 대시보드 ID를 통해 Graph 데이터를 조회하는 메서드
2327
*/
@@ -48,4 +52,20 @@ public void updateGraph(Integer dashboardId, BodyForReactFlow dto) {
4852
graph.getEdges().addAll(newEdges);
4953

5054
}
55+
56+
/**
57+
* 대시보드 접근 권한을 검증하는 메서드
58+
* @param member 접근을 시도하는 멤버
59+
* @param dashboardId 접근하려는 대시보드 ID
60+
*/
61+
public void verifyAccessPermission(Member member, Integer dashboardId) throws AccessDeniedException {
62+
Dashboard dashboard = dashboardRepository.findById(dashboardId)
63+
.orElseThrow(() -> new NoResultException(dashboardId + " ID를 가진 대시보드를 찾을 수 없습니다."));
64+
65+
try{
66+
membershipService.findByMemberAndSpace(member, dashboard.getSpace());
67+
} catch (NoResultException e) {
68+
throw new AccessDeniedException("대시보드의 접근 권한이 없습니다.");
69+
}
70+
}
5171
}
Lines changed: 175 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
package org.tuna.zoopzoop.backend.domain.dashboard.controller;
22

3-
import org.junit.jupiter.api.AfterEach;
4-
import org.junit.jupiter.api.BeforeEach;
5-
import org.junit.jupiter.api.DisplayName;
6-
import org.junit.jupiter.api.Test;
3+
import org.junit.jupiter.api.*;
74
import org.springframework.beans.factory.annotation.Autowired;
85
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
96
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
107
import org.springframework.boot.test.context.SpringBootTest;
118
import org.springframework.http.MediaType;
9+
import org.springframework.security.test.context.support.TestExecutionEvent;
10+
import org.springframework.security.test.context.support.WithUserDetails;
1211
import org.springframework.test.context.ActiveProfiles;
1312
import org.springframework.test.web.servlet.MockMvc;
13+
import org.springframework.test.web.servlet.ResultActions;
1414
import org.springframework.transaction.annotation.Transactional;
1515
import org.tuna.zoopzoop.backend.domain.dashboard.entity.Graph;
1616
import org.tuna.zoopzoop.backend.domain.dashboard.repository.GraphRepository;
17+
import org.tuna.zoopzoop.backend.domain.member.enums.Provider;
18+
import org.tuna.zoopzoop.backend.domain.member.service.MemberService;
19+
import org.tuna.zoopzoop.backend.domain.space.membership.enums.Authority;
20+
import org.tuna.zoopzoop.backend.domain.space.membership.service.MembershipService;
21+
import org.tuna.zoopzoop.backend.domain.space.space.entity.Space;
22+
import org.tuna.zoopzoop.backend.domain.space.space.service.SpaceService;
23+
import org.tuna.zoopzoop.backend.testSupport.ControllerTestSupport;
1724

1825
import static org.hamcrest.Matchers.hasSize;
1926
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -27,56 +34,189 @@
2734
@ActiveProfiles("test")
2835
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
2936
@Transactional
30-
class DashboardControllerTest {
37+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
38+
class DashboardControllerTest extends ControllerTestSupport {
39+
@Autowired
40+
private SpaceService spaceService;
3141

3242
@Autowired
33-
private MockMvc mockMvc;
43+
private MemberService memberService;
3444

3545
@Autowired
36-
private GraphRepository graphRepository;
46+
private MembershipService membershipService;
47+
48+
private Integer authorizedDashboardId;
49+
private Integer unauthorizedDashboardId;
3750

38-
@BeforeEach
51+
// 테스트에 필요한 유저, 스페이스, 멤버십 데이터를 미리 설정합니다.
52+
@BeforeAll
3953
void setUp() {
40-
graphRepository.deleteAll(); // 테스트 전 DB 초기화
54+
// 1. 유저 생성
55+
memberService.createMember("tester1_forDashboardControllerTest", "url", "dc1111", Provider.KAKAO);
56+
memberService.createMember("tester2_forDashboardControllerTest", "url", "dc2222", Provider.KAKAO);
57+
58+
// 2. 스페이스 생성 (생성과 동시에 대시보드도 생성됨)
59+
Space space1 = spaceService.createSpace("TestSpace1_forDashboardControllerTest", "thumb1");
60+
Space space2 = spaceService.createSpace("TestSpace2_forDashboardControllerTest", "thumb2");
61+
62+
// 테스트에서 사용할 대시보드 ID 저장
63+
this.authorizedDashboardId = space1.getDashboard().getId();
64+
this.unauthorizedDashboardId = space2.getDashboard().getId();
65+
66+
// 3. 멤버십 설정
67+
// user1은 Test Space 1에만 멤버로 가입 (접근 권한 있음)
68+
membershipService.addMemberToSpace(
69+
memberService.findByKakaoKey("dc1111"),
70+
space1,
71+
Authority.ADMIN
72+
);
73+
// user2는 Test Space 2에만 멤버로 가입
74+
membershipService.addMemberToSpace(
75+
memberService.findByKakaoKey("dc2222"),
76+
space2,
77+
Authority.ADMIN
78+
);
79+
}
80+
81+
// ============================= GET GRAPH ============================= //
82+
83+
@Test
84+
@WithUserDetails(value = "KAKAO:dc1111", setupBefore = TestExecutionEvent.TEST_METHOD)
85+
@DisplayName("대시보드 그래프 데이터 조회 - 성공")
86+
void getGraph_Success() throws Exception {
87+
// Given
88+
String url = String.format("/api/v1/dashboard/%d/graph", authorizedDashboardId);
89+
90+
// When
91+
ResultActions resultActions = performGet(url);
92+
93+
// Then
94+
expectOk(
95+
resultActions,
96+
String.format("ID: %d 의 React-flow 데이터를 조회했습니다.", authorizedDashboardId)
97+
);
98+
resultActions
99+
.andExpect(jsonPath("$.data.nodes").isArray())
100+
.andExpect(jsonPath("$.data.nodes").isEmpty())
101+
.andExpect(jsonPath("$.data.edges").isArray())
102+
.andExpect(jsonPath("$.data.edges").isEmpty());
41103
}
42104

43-
@AfterEach
44-
void cleanUp() {
45-
graphRepository.deleteAll(); // Graph만 삭제
46-
// 필요하면 다른 Repository도 순서대로 삭제
105+
@Test
106+
@WithUserDetails(value = "KAKAO:dc2222", setupBefore = TestExecutionEvent.TEST_METHOD)
107+
@DisplayName("대시보드 그래프 데이터 조회 - 실패: 접근 권한 없음")
108+
void getGraph_Fail_Forbidden() throws Exception {
109+
// Given
110+
// user2는 space1의 멤버가 아니므로, space1의 대시보드에 접근할 수 없음
111+
String url = String.format("/api/v1/dashboard/%d/graph", authorizedDashboardId);
112+
113+
// When
114+
ResultActions resultActions = performGet(url);
115+
116+
// Then
117+
// TODO: 실제 구현된 권한 체크 로직의 예외 메시지에 따라 "권한이 없습니다." 부분을 수정해야 합니다.
118+
expectForbidden(resultActions, "대시보드의 접근 권한이 없습니다.");
47119
}
48120

49-
// 단위 테스트가 백엔드 컨벡션 원칙이나, 서비스 특성 상 단위 테스트가 어려워
50-
// 일단 통합 테스트로 진행합니다.
51121
@Test
52-
@DisplayName("React-flow 데이터 저장 및 조회 테스트 - JSON 방식")
53-
void createAndGetGraphTest() throws Exception {
54-
// 테스트 용 React-flow JSON
55-
String jsonBody = """
122+
@WithUserDetails(value = "KAKAO:dc1111", setupBefore = TestExecutionEvent.TEST_METHOD)
123+
@DisplayName("대시보드 그래프 데이터 조회 - 실패: 존재하지 않는 대시보드")
124+
void getGraph_Fail_NotFound() throws Exception {
125+
// Given
126+
Integer nonExistentDashboardId = 9999;
127+
String url = String.format("/api/v1/dashboard/%d/graph", nonExistentDashboardId);
128+
129+
// When
130+
ResultActions resultActions = performGet(url);
131+
132+
// Then
133+
expectNotFound(
134+
resultActions,
135+
nonExistentDashboardId + " ID를 가진 대시보드를 찾을 수 없습니다."
136+
);
137+
}
138+
139+
// ============================= UPDATE GRAPH ============================= //
140+
141+
@Test
142+
@WithUserDetails(value = "KAKAO:dc1111", setupBefore = TestExecutionEvent.TEST_METHOD)
143+
@DisplayName("대시보드 그래프 데이터 저장 - 성공")
144+
void updateGraph_Success() throws Exception {
145+
// Given
146+
String url = String.format("/api/v1/dashboard/%d/graph", authorizedDashboardId);
147+
String requestBody = createReactFlowJsonBody();
148+
149+
// When: 데이터 수정
150+
ResultActions updateResult = performPut(url, requestBody);
151+
152+
// Then: 수정 성공 응답 확인
153+
expectOk(
154+
updateResult,
155+
"React-flow 데이터를 저장 했습니다."
156+
);
157+
158+
// When: 데이터 재조회하여 검증
159+
ResultActions getResult = performGet(url);
160+
161+
// Then: 재조회 결과가 수정한 데이터와 일치하는지 확인
162+
getResult
163+
.andExpect(jsonPath("$.data.nodes", hasSize(2)))
164+
.andExpect(jsonPath("$.data.edges", hasSize(1)))
165+
.andExpect(jsonPath("$.data.nodes[0].id").value("1"))
166+
.andExpect(jsonPath("$.data.nodes[0].data.title").value("노드1"))
167+
.andExpect(jsonPath("$.data.edges[0].id").value("e1-2"));
168+
}
169+
170+
@Test
171+
@DisplayName("대시보드 그래프 데이터 저장 - 실패: 존재하지 않는 대시보드")
172+
void updateGraph_Fail_NotFound() throws Exception {
173+
// Given
174+
Integer nonExistentDashboardId = 9999;
175+
String url = String.format("/api/v1/dashboard/%d/graph", nonExistentDashboardId);
176+
String requestBody = createReactFlowJsonBody();
177+
178+
// When
179+
ResultActions resultActions = performPut(url, requestBody);
180+
181+
// Then
182+
expectNotFound(
183+
resultActions,
184+
nonExistentDashboardId + " ID를 가진 대시보드를 찾을 수 없습니다."
185+
);
186+
}
187+
188+
// @Test
189+
// @DisplayName("대시보드 그래프 데이터 저장 - 실패: 서명 검증 실패")
190+
// void updateGraph_Fail_Forbidden() throws Exception {
191+
// // Given
192+
// String url = String.format("/api/v1/dashboard/%d/graph", authorizedDashboardId);
193+
// String requestBody = createReactFlowJsonBody();
194+
//
195+
// // When
196+
// ResultActions resultActions = performPut(url, requestBody);
197+
//
198+
// // Then
199+
// // TODO: 실제 구현된 권한 체크 로직의 예외 메시지에 따라 "권한이 없습니다." 부분을 수정해야 합니다.
200+
// expectForbidden(resultActions, "액세스가 거부되었습니다.");
201+
// }
202+
203+
// ======================= TEST DATA FACTORIES ======================== //
204+
205+
private String createReactFlowJsonBody() {
206+
return """
56207
{
57208
"nodes": [
58209
{
59210
"id": "1",
60211
"type": "CUSTOM",
61-
"data": {
62-
"title": "노드1",
63-
"description": "설명1"
64-
},
65-
"position": {
66-
"x": 100,
67-
"y": 200
68-
}
212+
"data": { "title": "노드1", "description": "설명1" },
213+
"position": { "x": 100, "y": 200 }
69214
},
70215
{
71216
"id": "2",
72217
"type": "CUSTOM",
73-
"data": {
74-
"title": "노드2"
75-
},
76-
"position": {
77-
"x": 300,
78-
"y": 400
79-
}
218+
"data": { "title": "노드2" },
219+
"position": { "x": 300, "y": 400 }
80220
}
81221
],
82222
"edges": [
@@ -86,44 +226,11 @@ void createAndGetGraphTest() throws Exception {
86226
"target": "2",
87227
"type": "SMOOTHSTEP",
88228
"animated": true,
89-
"style": {
90-
"stroke": "#999",
91-
"strokeWidth": 2.0
92-
}
229+
"style": { "stroke": "#999", "strokeWidth": 2.0 }
93230
}
94231
]
95232
}
96233
""";
97-
98-
// React-flow 데이터 저장
99-
mockMvc.perform(post("/api/v1/dashboard")
100-
.contentType(MediaType.APPLICATION_JSON)
101-
.content(jsonBody))
102-
.andExpect(status().isOk())
103-
.andExpect(jsonPath("$.status").value("200"))
104-
.andExpect(jsonPath("$.msg").value("React-flow 데이터를 저장 했습니다."))
105-
.andExpect(jsonPath("$.data").isEmpty());
106-
107-
// DB 무결성 검증
108-
assertEquals(1, graphRepository.count());
109-
Graph savedGraph = graphRepository.findAll().get(0);
110-
assertEquals(2, savedGraph.getNodes().size());
111-
assertEquals(1, savedGraph.getEdges().size());
112-
113-
// 저장된 React-flow 데이터 조회
114-
mockMvc.perform(get("/api/v1/dashboard/{dashboardId}", savedGraph.getId())
115-
.contentType(MediaType.APPLICATION_JSON))
116-
.andExpect(status().isOk())
117-
.andExpect(jsonPath("$.status").value("200"))
118-
.andExpect(jsonPath("$.msg").value("ID: " + savedGraph.getId() + " 의 React-flow 데이터를 조회했습니다."))
119-
.andExpect(jsonPath("$.data.nodes", hasSize(2)))
120-
.andExpect(jsonPath("$.data.edges", hasSize(1)))
121-
.andExpect(jsonPath("$.data.nodes[0].id").value("1"))
122-
.andExpect(jsonPath("$.data.nodes[0].type").value("CUSTOM"))
123-
.andExpect(jsonPath("$.data.nodes[0].data.title").value("노드1"))
124-
.andExpect(jsonPath("$.data.edges[0].id").value("e1-2"))
125-
.andExpect(jsonPath("$.data.edges[0].animated").value(true));
126234
}
127235

128-
129236
}

0 commit comments

Comments
 (0)