@Tag(name = "Diary", description = "일기 API")
@RestController
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@RequestMapping("/diaries")
public class DiaryController {
private final DiaryService diaryService;
private final S3UploadService s3UploadService;
@Operation(summary = "일기 등록")
@PostMapping
public ResponseEntity<ResultResponse> createDiary(@AuthenticationPrincipal UserDetail user,
@RequestPart("file") MultipartFile multipartFile,
@Valid @RequestPart(value="createRequest") DiaryCreateReq createRequest) throws IOException {
String drawingUrl = s3UploadService.saveFile(multipartFile, user.getUsername());
diaryService.createDiary(user, createRequest, drawingUrl);
return ResponseEntity.ok(ResultResponse.of(ResultCode.DIARY_CREATE_SUCCESS));
}
}
@AuthenticationPrincipal UserDetail user
이 매개 변수로 붙여져 있습니다.CustomUserDetailsService
의 loadUserByUsername
이라는 메서드가 return한 객체를 파라미터로 받아 사용할 수 있도록 해주는 어노테이션
@Service
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class CustomUserDetailService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User principal = userRepository.findUserByEmail(email)
.orElseThrow(UserNotFoundException::new);
return new UserDetail(principal);
}
}
Spring Security가 적용된 곳을 효율적으로 테스트하자.
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDetail implements UserDetails {
private User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collections = new ArrayList<>();
collections.add(() -> "ROLE_" + user.getRole());
return collections;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public Long getUserId(){
return user.getId();
}
}
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithCustomMockUserSecurityContextFactory.class)
public @interface WithCustomMockUser {
String userUuid() default "test@email";
String role() default "USER";
}
public class WithCustomMockUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser> {
@Override
public SecurityContext createSecurityContext(WithCustomMockUser annotation) {
String userEmail = annotation.userUuid();
String role = annotation.role();
User user = User.builder()
.email(userEmail)
.nickname("테스트 유저")
.password("Test012@")
.role(Role.valueOf(role))
.build();
UserDetail userDetail = UserDetail.builder().user(user).build();
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(userDetail, "password", List.of(new SimpleGrantedAuthority(role)));
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(token);
return context;
}
}
@WebMvcTest(DiaryController.class)
@ExtendWith(RestDocumentationExtension.class)
@DisplayName("Diary 컨트롤러에 ")
class DiaryControllerTest extends MockApiTest {
@MockBean
private DiaryService diaryService;
@MockBean
private S3UploadService s3UploadService;
@Test
@WithCustomMockUser
@DisplayName("일기가 등록될 수 있다.")
void createDiary() throws Exception {
//given
MockMultipartFile file = new MockMultipartFile(
"file", // 파라미터 이름
"file_name", // 파일 이름
"image/jpeg",// 파일 타입
"test file".getBytes(StandardCharsets.UTF_8) // 파일 내용
);
given(s3UploadService.saveFile(any(), any())).willReturn(String.valueOf(file));
DiaryCreateReq createReq = DiaryControllerFixture.CREATE_REQ;
String jsonByCreateReq = objectMapper.writeValueAsString(createReq);
MockMultipartFile request = new MockMultipartFile(
"createRequest",
"createRequest",
"application/json",
jsonByCreateReq.getBytes(StandardCharsets.UTF_8)
);
String token = "accessToken";
diaryService.createDiary(any(), any(), any());
//when
ResultActions perform =
mockMvc.perform(
multipart("/diaries")
.file(file)
.file(request)
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
);
// then
perform.andExpect(status().isOk());
// docs
perform.andDo(print())
.andDo(document("register diary",
getDocumentRequest(),
getDocumentResponse()));
}
[Spring Security] @AuthenticationPrincipal 유닛 테스트 - Custom Mock User 삽입하기