11package 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 .*;
74import org .springframework .beans .factory .annotation .Autowired ;
85import org .springframework .boot .test .autoconfigure .jdbc .AutoConfigureTestDatabase ;
96import org .springframework .boot .test .autoconfigure .web .servlet .AutoConfigureMockMvc ;
107import org .springframework .boot .test .context .SpringBootTest ;
118import org .springframework .http .MediaType ;
9+ import org .springframework .security .test .context .support .TestExecutionEvent ;
10+ import org .springframework .security .test .context .support .WithUserDetails ;
1211import org .springframework .test .context .ActiveProfiles ;
1312import org .springframework .test .web .servlet .MockMvc ;
13+ import org .springframework .test .web .servlet .ResultActions ;
1414import org .springframework .transaction .annotation .Transactional ;
1515import org .tuna .zoopzoop .backend .domain .dashboard .entity .Graph ;
1616import 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
1825import static org .hamcrest .Matchers .hasSize ;
1926import static org .junit .jupiter .api .Assertions .assertEquals ;
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