1+ package com .back .domain .mybar .controller ;
2+
3+ import com .back .domain .mybar .dto .MyBarItemResponseDto ;
4+ import com .back .domain .mybar .dto .MyBarListResponseDto ;
5+ import com .back .domain .mybar .service .MyBarService ;
6+ import com .back .global .aspect .ResponseAspect ;
7+ import com .back .global .jwt .JwtUtil ;
8+ import com .back .global .rq .Rq ;
9+ import com .back .global .security .SecurityUser ;
10+ import java .time .LocalDateTime ;
11+ import java .time .format .DateTimeFormatter ;
12+ import java .util .List ;
13+ import java .util .Map ;
14+ import org .junit .jupiter .api .AfterEach ;
15+ import org .junit .jupiter .api .DisplayName ;
16+ import org .junit .jupiter .api .Test ;
17+ import org .springframework .beans .factory .annotation .Autowired ;
18+ import org .springframework .boot .autoconfigure .ImportAutoConfiguration ;
19+ import org .springframework .boot .autoconfigure .aop .AopAutoConfiguration ;
20+ import org .springframework .boot .test .autoconfigure .web .servlet .AutoConfigureMockMvc ;
21+ import org .springframework .boot .test .autoconfigure .web .servlet .WebMvcTest ;
22+ import org .springframework .context .annotation .Import ;
23+ import org .springframework .http .MediaType ;
24+ import org .springframework .security .authentication .UsernamePasswordAuthenticationToken ;
25+ import org .springframework .security .core .authority .SimpleGrantedAuthority ;
26+ import org .springframework .security .core .context .SecurityContext ;
27+ import org .springframework .security .core .context .SecurityContextHolder ;
28+ import org .springframework .security .web .context .HttpSessionSecurityContextRepository ;
29+ import org .springframework .test .context .bean .override .mockito .MockitoBean ;
30+ import org .springframework .test .web .servlet .MockMvc ;
31+ import org .springframework .test .web .servlet .request .RequestPostProcessor ;
32+
33+ import static org .mockito .ArgumentMatchers .eq ;
34+ import static org .mockito .ArgumentMatchers .isNull ;
35+ import static org .mockito .BDDMockito .given ;
36+ import static org .mockito .BDDMockito .willDoNothing ;
37+ import static org .mockito .Mockito .verify ;
38+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .delete ;
39+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .get ;
40+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .post ;
41+ import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .jsonPath ;
42+ import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .status ;
43+
44+ @ WebMvcTest (MyBarController .class )
45+ @ AutoConfigureMockMvc (addFilters = false )
46+ @ Import (ResponseAspect .class )
47+ @ ImportAutoConfiguration (AopAutoConfiguration .class )
48+ class MyBarControllerTest {
49+
50+ private static final DateTimeFormatter ISO_WITH_SECONDS = DateTimeFormatter .ofPattern ("yyyy-MM-dd'T'HH:mm:ss" );
51+
52+ @ Autowired
53+ private MockMvc mockMvc ;
54+
55+ @ MockitoBean
56+ private MyBarService myBarService ;
57+
58+ @ MockitoBean
59+ private JwtUtil jwtUtil ;
60+
61+ @ MockitoBean
62+ private Rq rq ;
63+
64+ @ AfterEach
65+ void clearSecurityContext () {
66+ SecurityContextHolder .clearContext ();
67+ }
68+
69+ private SecurityUser createPrincipal (Long userId ) {
70+ return new SecurityUser (
71+ userId ,
72+ "user" + userId + "@example.com" ,
73+ "user" + userId ,
74+ false ,
75+ List .of (new SimpleGrantedAuthority ("ROLE_USER" )),
76+ Map .of ()
77+ );
78+ }
79+
80+ private UsernamePasswordAuthenticationToken authenticated (SecurityUser principal ) {
81+ return new UsernamePasswordAuthenticationToken (principal , null , principal .getAuthorities ());
82+ }
83+
84+ private RequestPostProcessor withPrincipal (SecurityUser principal ) {
85+ return request -> {
86+ UsernamePasswordAuthenticationToken authentication = authenticated (principal );
87+ SecurityContext context = SecurityContextHolder .createEmptyContext ();
88+ context .setAuthentication (authentication );
89+ SecurityContextHolder .setContext (context );
90+ request .setUserPrincipal (authentication );
91+ request .getSession (true )
92+ .setAttribute (HttpSessionSecurityContextRepository .SPRING_SECURITY_CONTEXT_KEY , context );
93+ return request ;
94+ };
95+ }
96+
97+ @ Test
98+ @ DisplayName ("Get my bar list - first page" )
99+ void getMyBarList_withoutCursor () throws Exception {
100+ SecurityUser principal = createPrincipal (1L );
101+ LocalDateTime keptAt = LocalDateTime .of (2025 , 1 , 1 , 10 , 0 );
102+ LocalDateTime createdAt = keptAt .minusDays (1 );
103+
104+ MyBarItemResponseDto item = MyBarItemResponseDto .builder ()
105+ .id (3L )
106+ .cocktailId (10L )
107+ .cocktailName ("Margarita" )
108+ .imageUrl ("https://example.com/margarita.jpg" )
109+ .createdAt (createdAt )
110+ .keptAt (keptAt )
111+ .build ();
112+
113+ MyBarListResponseDto responseDto = new MyBarListResponseDto (
114+ List .of (item ),
115+ true ,
116+ keptAt .minusMinutes (5 ),
117+ 2L
118+ );
119+
120+ given (myBarService .getMyBar (
121+ eq (principal .getId ()),
122+ isNull (LocalDateTime .class ),
123+ isNull (Long .class ),
124+ eq (20 )
125+ )).willReturn (responseDto );
126+
127+ mockMvc .perform (get ("/me/bar" )
128+ .with (withPrincipal (principal ))
129+ .accept (MediaType .APPLICATION_JSON ))
130+ .andExpect (status ().isOk ())
131+ .andExpect (jsonPath ("$.code" ).value (200 ))
132+ .andExpect (jsonPath ("$.message" ).value ("success" ))
133+ .andExpect (jsonPath ("$.data.items[0].id" ).value (3L ))
134+ .andExpect (jsonPath ("$.data.items[0].cocktailId" ).value (10L ))
135+ .andExpect (jsonPath ("$.data.items[0].cocktailName" ).value ("Margarita" ))
136+ .andExpect (jsonPath ("$.data.items[0].imageUrl" ).value ("https://example.com/margarita.jpg" ))
137+ .andExpect (jsonPath ("$.data.items[0].createdAt" ).value (ISO_WITH_SECONDS .format (createdAt )))
138+ .andExpect (jsonPath ("$.data.items[0].keptAt" ).value (ISO_WITH_SECONDS .format (keptAt )))
139+ .andExpect (jsonPath ("$.data.hasNext" ).value (true ))
140+ .andExpect (jsonPath ("$.data.nextKeptAt" ).value (ISO_WITH_SECONDS .format (keptAt .minusMinutes (5 ))))
141+ .andExpect (jsonPath ("$.data.nextId" ).value (2L ));
142+
143+ verify (myBarService ).getMyBar (
144+ eq (principal .getId ()),
145+ isNull (LocalDateTime .class ),
146+ isNull (Long .class ),
147+ eq (20 )
148+ );
149+ }
150+
151+ @ Test
152+ @ DisplayName ("Get my bar list - next page" )
153+ void getMyBarList_withCursor () throws Exception {
154+ SecurityUser principal = createPrincipal (7L );
155+ LocalDateTime cursorKeptAt = LocalDateTime .of (2025 , 2 , 10 , 9 , 30 , 15 );
156+ LocalDateTime itemKeptAt = cursorKeptAt .minusMinutes (1 );
157+ LocalDateTime itemCreatedAt = itemKeptAt .minusDays (2 );
158+
159+ MyBarItemResponseDto item = MyBarItemResponseDto .builder ()
160+ .id (20L )
161+ .cocktailId (33L )
162+ .cocktailName ("Negroni" )
163+ .imageUrl ("https://example.com/negroni.jpg" )
164+ .createdAt (itemCreatedAt )
165+ .keptAt (itemKeptAt )
166+ .build ();
167+
168+ MyBarListResponseDto responseDto = new MyBarListResponseDto (
169+ List .of (item ),
170+ false ,
171+ null ,
172+ null
173+ );
174+
175+ given (myBarService .getMyBar (
176+ eq (principal .getId ()),
177+ eq (cursorKeptAt ),
178+ eq (99L ),
179+ eq (5 )
180+ )).willReturn (responseDto );
181+
182+ mockMvc .perform (get ("/me/bar" )
183+ .with (withPrincipal (principal ))
184+ .param ("lastKeptAt" , cursorKeptAt .toString ())
185+ .param ("lastId" , "99" )
186+ .param ("limit" , "5" )
187+ .accept (MediaType .APPLICATION_JSON ))
188+ .andExpect (status ().isOk ())
189+ .andExpect (jsonPath ("$.code" ).value (200 ))
190+ .andExpect (jsonPath ("$.message" ).value ("success" ))
191+ .andExpect (jsonPath ("$.data.items[0].id" ).value (20L ))
192+ .andExpect (jsonPath ("$.data.items[0].cocktailName" ).value ("Negroni" ))
193+ .andExpect (jsonPath ("$.data.hasNext" ).value (false ))
194+ .andExpect (jsonPath ("$.data.nextKeptAt" ).doesNotExist ());
195+
196+ verify (myBarService ).getMyBar (
197+ eq (principal .getId ()),
198+ eq (cursorKeptAt ),
199+ eq (99L ),
200+ eq (5 )
201+ );
202+ }
203+
204+ @ Test
205+ @ DisplayName ("Keep cocktail" )
206+ void keepCocktail () throws Exception {
207+ SecurityUser principal = createPrincipal (11L );
208+ Long cocktailId = 42L ;
209+
210+ willDoNothing ().given (myBarService ).keep (principal .getId (), cocktailId );
211+
212+ mockMvc .perform (post ("/me/bar/{cocktailId}/keep" , cocktailId )
213+ .with (withPrincipal (principal ))
214+ .accept (MediaType .APPLICATION_JSON ))
215+ .andExpect (status ().isCreated ())
216+ .andExpect (jsonPath ("$.code" ).value (201 ))
217+ .andExpect (jsonPath ("$.message" ).value ("kept" ))
218+ .andExpect (jsonPath ("$.data" ).doesNotExist ());
219+
220+ verify (myBarService ).keep (principal .getId (), cocktailId );
221+ }
222+
223+ @ Test
224+ @ DisplayName ("Unkeep cocktail" )
225+ void unkeepCocktail () throws Exception {
226+ SecurityUser principal = createPrincipal (12L );
227+ Long cocktailId = 77L ;
228+
229+ willDoNothing ().given (myBarService ).unkeep (principal .getId (), cocktailId );
230+
231+ mockMvc .perform (delete ("/me/bar/{cocktailId}/keep" , cocktailId )
232+ .with (withPrincipal (principal ))
233+ .accept (MediaType .APPLICATION_JSON ))
234+ .andExpect (status ().isOk ())
235+ .andExpect (jsonPath ("$.code" ).value (200 ))
236+ .andExpect (jsonPath ("$.message" ).value ("deleted" ))
237+ .andExpect (jsonPath ("$.data" ).doesNotExist ());
238+
239+ verify (myBarService ).unkeep (principal .getId (), cocktailId );
240+ }
241+ }
0 commit comments