1+ package com .back .domain .scenario .controller ;
2+
3+ import com .back .domain .scenario .dto .*;
4+ import com .back .domain .scenario .entity .ScenarioStatus ;
5+ import com .back .domain .scenario .entity .Type ;
6+ import com .back .domain .scenario .service .ScenarioService ;
7+ import com .back .global .exception .ApiException ;
8+ import com .back .global .exception .ErrorCode ;
9+ import com .fasterxml .jackson .databind .ObjectMapper ;
10+ import org .junit .jupiter .api .DisplayName ;
11+ import org .junit .jupiter .api .Nested ;
12+ import org .junit .jupiter .api .Test ;
13+ import org .junit .jupiter .api .TestInstance ;
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 .boot .test .mock .mockito .MockBean ;
18+ import org .springframework .http .MediaType ;
19+ import org .springframework .test .context .ActiveProfiles ;
20+ import org .springframework .test .web .servlet .MockMvc ;
21+ import org .springframework .transaction .annotation .Transactional ;
22+
23+ import java .time .LocalDateTime ;
24+ import java .util .List ;
25+
26+ import static org .mockito .ArgumentMatchers .*;
27+ import static org .mockito .BDDMockito .given ;
28+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .*;
29+ import static org .springframework .test .web .servlet .result .MockMvcResultHandlers .print ;
30+ import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .*;
31+
32+ /**
33+ * ScenarioController 통합 테스트.
34+ * 인증/인가가 구현되지 않은 상태에서 Service를 모킹하여 테스트합니다.
35+ */
36+ @ SpringBootTest
37+ @ AutoConfigureMockMvc (addFilters = false )
38+ @ ActiveProfiles ("test" )
39+ @ Transactional
40+ @ TestInstance (TestInstance .Lifecycle .PER_CLASS )
41+ @ DisplayName ("ScenarioController 통합 테스트" )
42+ class ScenarioControllerTest {
43+
44+ @ Autowired
45+ private MockMvc mockMvc ;
46+
47+ @ Autowired
48+ private ObjectMapper objectMapper ;
49+
50+ @ MockBean
51+ private ScenarioService scenarioService ;
52+
53+ @ Nested
54+ @ DisplayName ("시나리오 생성" )
55+ class CreateScenario {
56+
57+ @ Test
58+ @ DisplayName ("성공 - 정상적인 시나리오 생성 요청" )
59+ void createScenario_성공 () throws Exception {
60+ // Given
61+ Long decisionLineId = 100L ;
62+ ScenarioCreateRequest request = new ScenarioCreateRequest (decisionLineId );
63+
64+ ScenarioStatusResponse mockResponse = new ScenarioStatusResponse (
65+ 1001L ,
66+ ScenarioStatus .PENDING ,
67+ "시나리오 생성이 시작되었습니다."
68+ );
69+
70+ given (scenarioService .createScenario (eq (1L ), any (ScenarioCreateRequest .class )))
71+ .willReturn (mockResponse );
72+
73+ // When & Then
74+ mockMvc .perform (post ("/api/v1/scenarios" )
75+ .contentType (MediaType .APPLICATION_JSON )
76+ .content (objectMapper .writeValueAsString (request )))
77+ .andDo (print ())
78+ .andExpect (status ().isOk ()) // ApiResponse는 실제 HTTP 상태를 설정하지 않음
79+ .andExpect (jsonPath ("$.data.scenarioId" ).value (1001 ))
80+ .andExpect (jsonPath ("$.data.status" ).value ("PENDING" ))
81+ .andExpect (jsonPath ("$.message" ).value ("시나리오 생성 요청이 접수되었습니다." ))
82+ .andExpect (jsonPath ("$.status" ).value (201 )); // 응답 본문의 status 필드 검증
83+ }
84+
85+ @ Test
86+ @ DisplayName ("실패 - 잘못된 요청 데이터 (null decisionLineId)" )
87+ void createScenario_실패_잘못된요청 () throws Exception {
88+ // Given
89+ String invalidRequest = "{\" decisionLineId\" :null}" ;
90+
91+ // When & Then
92+ mockMvc .perform (post ("/api/v1/scenarios" )
93+ .contentType (MediaType .APPLICATION_JSON )
94+ .content (invalidRequest ))
95+ .andDo (print ())
96+ .andExpect (status ().isBadRequest ());
97+ }
98+
99+ @ Test
100+ @ DisplayName ("실패 - Service 예외 발생" )
101+ void createScenario_실패_Service예외 () throws Exception {
102+ // Given
103+ ScenarioCreateRequest request = new ScenarioCreateRequest (999L );
104+
105+ given (scenarioService .createScenario (eq (1L ), any (ScenarioCreateRequest .class )))
106+ .willThrow (new ApiException (ErrorCode .DECISION_LINE_NOT_FOUND ));
107+
108+ // When & Then
109+ mockMvc .perform (post ("/api/v1/scenarios" )
110+ .contentType (MediaType .APPLICATION_JSON )
111+ .content (objectMapper .writeValueAsString (request )))
112+ .andDo (print ())
113+ .andExpect (status ().isNotFound ());
114+ }
115+ }
116+
117+ @ Nested
118+ @ DisplayName ("시나리오 상태 조회" )
119+ class GetScenarioStatus {
120+
121+ @ Test
122+ @ DisplayName ("성공 - 유효한 시나리오 상태 조회" )
123+ void getScenarioStatus_성공 () throws Exception {
124+ // Given
125+ Long scenarioId = 1001L ;
126+ ScenarioStatusResponse mockResponse = new ScenarioStatusResponse (
127+ scenarioId ,
128+ ScenarioStatus .COMPLETED ,
129+ "시나리오 생성이 완료되었습니다."
130+ );
131+
132+ given (scenarioService .getScenarioStatus (scenarioId , 1L ))
133+ .willReturn (mockResponse );
134+
135+ // When & Then
136+ mockMvc .perform (get ("/api/v1/scenarios/{scenarioId}/status" , scenarioId ))
137+ .andDo (print ())
138+ .andExpect (status ().isOk ())
139+ .andExpect (jsonPath ("$.data.scenarioId" ).value (scenarioId ))
140+ .andExpect (jsonPath ("$.data.status" ).value ("COMPLETED" ))
141+ .andExpect (jsonPath ("$.message" ).value ("상태를 성공적으로 조회했습니다." ));
142+ }
143+
144+ @ Test
145+ @ DisplayName ("실패 - 존재하지 않는 시나리오" )
146+ void getScenarioStatus_실패_없는시나리오 () throws Exception {
147+ // Given
148+ Long scenarioId = 999L ;
149+
150+ given (scenarioService .getScenarioStatus (scenarioId , 1L ))
151+ .willThrow (new ApiException (ErrorCode .SCENARIO_NOT_FOUND ));
152+
153+ // When & Then
154+ mockMvc .perform (get ("/api/v1/scenarios/{scenarioId}/status" , scenarioId ))
155+ .andDo (print ())
156+ .andExpect (status ().isNotFound ());
157+ }
158+ }
159+
160+ @ Nested
161+ @ DisplayName ("시나리오 상세 조회" )
162+ class GetScenarioDetail {
163+
164+ @ Test
165+ @ DisplayName ("성공 - 완료된 시나리오 상세 조회" )
166+ void getScenarioDetail_성공 () throws Exception {
167+ // Given
168+ Long scenarioId = 1001L ;
169+
170+ List <ScenarioTypeDto > indicators = List .of (
171+ new ScenarioTypeDto (Type .경제 , 90 , "창업 성공으로 높은 경제적 성취" ),
172+ new ScenarioTypeDto (Type .행복 , 85 , "자신이 원하는 일을 하며 성취감 높음" ),
173+ new ScenarioTypeDto (Type .관계 , 75 , "업무로 인해 개인 관계에 다소 소홀" ),
174+ new ScenarioTypeDto (Type .직업 , 95 , "창업가로서 최고 수준의 직업 만족도" ),
175+ new ScenarioTypeDto (Type .건강 , 70 , "스트레스로 인한 건강 관리 필요" )
176+ );
177+
178+ ScenarioDetailResponse mockResponse = new ScenarioDetailResponse (
179+ scenarioId ,
180+ ScenarioStatus .COMPLETED ,
181+ "스타트업 CEO" ,
182+ 85 ,
183+ "성공적인 창업으로 안정적인 수익 창출" ,
184+ "창업 초기 어려움을 극복하고 지속가능한 비즈니스 모델을 구축했습니다." ,
185+ "https://example.com/scenario-image.jpg" ,
186+ LocalDateTime .now ().minusDays (1 ),
187+ indicators
188+ );
189+
190+ given (scenarioService .getScenarioDetail (scenarioId , 1L ))
191+ .willReturn (mockResponse );
192+
193+ // When & Then
194+ mockMvc .perform (get ("/api/v1/scenarios/info/{scenarioId}" , scenarioId ))
195+ .andDo (print ())
196+ .andExpect (status ().isOk ())
197+ .andExpect (jsonPath ("$.data.scenarioId" ).value (scenarioId ))
198+ .andExpect (jsonPath ("$.data.job" ).value ("스타트업 CEO" ))
199+ .andExpect (jsonPath ("$.data.total" ).value (85 ))
200+ .andExpect (jsonPath ("$.data.indicators" ).isArray ())
201+ .andExpect (jsonPath ("$.data.indicators.length()" ).value (5 ));
202+ }
203+ }
204+
205+ @ Nested
206+ @ DisplayName ("시나리오 타임라인 조회" )
207+ class GetScenarioTimeline {
208+
209+ @ Test
210+ @ DisplayName ("성공 - 시나리오 타임라인 조회" )
211+ void getScenarioTimeline_성공 () throws Exception {
212+ // Given
213+ Long scenarioId = 1001L ;
214+
215+ List <TimelineResponse .TimelineEvent > events = List .of (
216+ new TimelineResponse .TimelineEvent (2025 , "창업 시작" ),
217+ new TimelineResponse .TimelineEvent (2027 , "첫 투자 유치" ),
218+ new TimelineResponse .TimelineEvent (2030 , "IPO 성공" )
219+ );
220+
221+ TimelineResponse mockResponse = new TimelineResponse (scenarioId , events );
222+
223+ given (scenarioService .getScenarioTimeline (scenarioId , 1L ))
224+ .willReturn (mockResponse );
225+
226+ // When & Then
227+ mockMvc .perform (get ("/api/v1/scenarios/{scenarioId}/timeline" , scenarioId ))
228+ .andDo (print ())
229+ .andExpect (status ().isOk ())
230+ .andExpect (jsonPath ("$.data.scenarioId" ).value (scenarioId ))
231+ .andExpect (jsonPath ("$.data.events" ).isArray ())
232+ .andExpect (jsonPath ("$.data.events[0].year" ).value (2025 ))
233+ .andExpect (jsonPath ("$.data.events[0].title" ).value ("창업 시작" ));
234+ }
235+ }
236+
237+ @ Nested
238+ @ DisplayName ("베이스라인 목록 조회" )
239+ class GetBaselines {
240+
241+ @ Test
242+ @ DisplayName ("성공 - 사용자 베이스라인 목록 조회" )
243+ void getBaselines_성공 () throws Exception {
244+ // Given
245+ List <BaselineListResponse > mockResponse = List .of (
246+ new BaselineListResponse (
247+ 200L ,
248+ "대학 졸업 이후" ,
249+ List .of ("진로" , "교육" ),
250+ LocalDateTime .now ().minusMonths (6 )
251+ ),
252+ new BaselineListResponse (
253+ 201L ,
254+ "첫 직장 입사" ,
255+ List .of ("직업" , "성장" ),
256+ LocalDateTime .now ().minusMonths (3 )
257+ )
258+ );
259+
260+ given (scenarioService .getBaselines (1L ))
261+ .willReturn (mockResponse );
262+
263+ // When & Then
264+ mockMvc .perform (get ("/api/v1/scenarios/baselines" ))
265+ .andDo (print ())
266+ .andExpect (status ().isOk ())
267+ .andExpect (jsonPath ("$.data" ).isArray ())
268+ .andExpect (jsonPath ("$.data[0].baselineId" ).value (200 ))
269+ .andExpect (jsonPath ("$.data[0].title" ).value ("대학 졸업 이후" ));
270+ }
271+ }
272+
273+ @ Nested
274+ @ DisplayName ("시나리오 비교" )
275+ class CompareScenarios {
276+
277+ @ Test
278+ @ DisplayName ("성공 - 시나리오 비교 분석" )
279+ void compareScenarios_성공 () throws Exception {
280+ // Given
281+ Long baseId = 1001L ;
282+ Long compareId = 1002L ;
283+
284+ List <ScenarioCompareResponse .IndicatorComparison > indicators = List .of (
285+ new ScenarioCompareResponse .IndicatorComparison (Type .경제 , 90 , 80 , "창업이 대기업보다 경제적으로 유리" ),
286+ new ScenarioCompareResponse .IndicatorComparison (Type .행복 , 85 , 70 , "창업을 통한 더 높은 성취감" ),
287+ new ScenarioCompareResponse .IndicatorComparison (Type .관계 , 75 , 85 , "대기업에서 더 안정적인 인간관계" ),
288+ new ScenarioCompareResponse .IndicatorComparison (Type .직업 , 95 , 75 , "창업가로서 더 높은 직업 만족도" ),
289+ new ScenarioCompareResponse .IndicatorComparison (Type .건강 , 70 , 80 , "대기업에서 더 나은 워라밸" )
290+ );
291+
292+ ScenarioCompareResponse mockResponse = new ScenarioCompareResponse (
293+ baseId ,
294+ compareId ,
295+ "창업 경로가 전반적으로 더 도전적이지만 성취감과 경제적 보상이 큽니다." ,
296+ indicators
297+ );
298+
299+ given (scenarioService .compareScenarios (baseId , compareId , 1L ))
300+ .willReturn (mockResponse );
301+
302+ // When & Then
303+ mockMvc .perform (get ("/api/v1/scenarios/compare/{baseId}/{compareId}" , baseId , compareId ))
304+ .andDo (print ())
305+ .andExpect (status ().isOk ())
306+ .andExpect (jsonPath ("$.data.baseScenarioId" ).value (baseId ))
307+ .andExpect (jsonPath ("$.data.compareScenarioId" ).value (compareId ))
308+ .andExpect (jsonPath ("$.data.overallAnalysis" ).exists ())
309+ .andExpect (jsonPath ("$.data.indicators" ).isArray ());
310+ }
311+ }
312+ }
0 commit comments