SNS 제작 (포스트 상세 조회, 포스트 수정)

개발연습생log·2022년 12월 27일
0

SNS 제작

목록 보기
5/15
post-thumbnail

✨개요

🏃 목표

📢 포스트 상세 조회, 수정 기능을 구현하자.

📢 요구사항

  • 포스트 상세
    • 회원, 비회원 모두 볼 수 있다.
    • GET /posts/{postsId}
    • id, 제목, 내용, 작성자, 작성날짜, 수정날짜
    • 리턴 (JSON 형식)
      {
      	"resultCode":"SUCCESS",
      	"result":{
      		"id" : 1,
      		"title" : "title1",
      		"body" : "body",
      		"userName" : "user1",
      		"createdAt" : yyyy-mm-dd hh:mm:ss,
      		"lastModifiedAt" : yyyy-mm-dd hh:mm:ss
      	}
      }
      
  • 포스트 수정
    • ADMIN회원이나 글을 작성한 일반회원이 글에 대한 수정과 삭제를 할 수 있다.
    • PUT /posts/{id}
    • 입력폼 (JSON 형식)
      {
      	"title" : "modified title",
      	"body" : "modified body"
      }
    • 리턴 (JSON 형식)
      {
      	"resultCode":"SUCCESS",
      	"result":{
      		"message":"포스트 수정 완료",
      		"postId":0
      	}
      }

✅ TO-DO

  • 포스트 상세조회 컨트롤러, 서비스 테스트 작성
  • 포스트 상세조회 컨트롤러 구현
  • 포스트 상세조회 서비스 구현
  • SecurityConfig 수정
  • 포스트 수정 컨트롤러, 서비스 테스트 작성
  • 포스트 수정 컨트롤러 구현
  • 포스트 수정 서비스 구현

🔧 구현

포스트 상세조회 컨트롤러, 서비스 테스트 작성

컨트롤러 테스트 메서드 추가

<@Test
    @DisplayName("포스트 상세 조회 성공")
    @WithMockUser
    void post_get_detail_SUCCESS() throws Exception {
        String title = "테스트";
        String body = "테스트입니다.";

        when(postService.getPost(any()))
                .thenReturn(postGetResponse);

        mockMvc.perform(get("/api/v1/posts/1")
                        .with(csrf()))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.result.id").exists())
                .andExpect(jsonPath("$.result.title").exists())
                .andExpect(jsonPath("$.result.body").exists())
                .andExpect(jsonPath("$.result.userName").exists());
    }

서비스 테스트 메서드 추가

@Test
    @DisplayName("포스트 상세조회 성공")
    void post_get_detail_SUCCESS() {
        when(postRepository.findById(any()))
                .thenReturn(Optional.of(post));

        PostGetResponse postGetResponse = postService.getPost(post.getId());
        assertEquals(postGetResponse.getUserName(),post.getUser().getUserName());
    }

포스트 상세조회 컨트롤러 구현

PostController 메서드 추가

@GetMapping("/{id}")
    public ResponseEntity<Response> getPost(@PathVariable Long id) {
        PostGetResponse postGetResponse = postService.getPost(id);
        return ResponseEntity.ok().body(Response.of("SUCCESS", postGetResponse));
    }

PostGetResponse DTO 구현

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Setter
@Getter
public class PostGetResponse {
    private Long id;
    private String title;
    private String body;
    private String userName;
    private LocalDateTime createdAt;
    private LocalDateTime lastModifiedAt;
}

포스트 상세조회 서비스 구현

PostService 메서드 추가

public PostGetResponse getPost(Long id) {
        //해당 id 포스트 찾기
        Post findPost = postRepository.findById(id).orElseThrow(() -> {
            throw new AppException(ErrorCode.POST_NOT_FOUND,ErrorCode.POST_NOT_FOUND.getMessage());
        });
        //포스트 응답 DTO 변환 후 리턴
        PostGetResponse postGetResponse = findPost.toResponse();
        return postGetResponse;
    }

Post 메서드 추가

public PostGetResponse toResponse(){
        return PostGetResponse.builder()
                .id(this.id)
                .userName(this.user.getUserName())
                .title(this.title)
                .body(this.body)
                .createdAt(getCreatedAt())
                .lastModifiedAt(getModifiedAt())
                .build();
    }

SecurityConfig 수정

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

    private final AuthenticationManager authenticationManager;
    private final JwtFilter jwtFilter;
    private String[] PERMIT_URL = {
            "/api/v1/hello",
            "/api/v1/users/**"
    };

    private String[] SWAGGER = {
            /* swagger v2 */
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/webjars/**",
            /* swagger v3 */
            "/v3/api-docs/**",
            "/swagger-ui/**"
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity.httpBasic().disable()
                .csrf().disable()
                .cors().and()
                .authorizeRequests()
                .antMatchers(SWAGGER).permitAll()
                .antMatchers(PERMIT_URL).permitAll()
                .antMatchers(HttpMethod.GET).permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling().authenticationEntryPoint(authenticationManager)
                .and()
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}

포스트 수정 컨트롤러, 서비스 테스트 작성

포스트 수정 컨트롤러 테스트 메서드 작성

@Test
    @DisplayName("포스트 수정 성공")
    @WithMockUser
    void post_modify_SUCCESS() throws Exception {
        String title = "테스트";
        String body = "테스트입니다.";

        when(postService.modify(any(),any(),any(),any()))
                .thenReturn(new PostResponse(title, 1l));

        mockMvc.perform(put("/api/v1/posts/1")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new PostRequest(title, body))))
                .andDo(print())
                .andExpect(status().isOk());
    }

    @Test
    @DisplayName("포스트 수정 실패1_인증")
    @WithAnonymousUser
    void post_modify_FAILED_authentication() throws Exception {
        String title = "테스트";
        String body = "테스트입니다.";

        when(postService.modify(any(),any(),any(),any()))
                .thenThrow(new AppException(ErrorCode.INVALID_PERMISSION,ErrorCode.INVALID_PERMISSION.getMessage()));

        mockMvc.perform(put("/api/v1/posts/1")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new PostRequest(title, body))))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("포스트 수정 실패2_작성자 불일치")
    @WithMockUser
    void post_modify_FAILED_different() throws Exception {
        String title = "테스트";
        String body = "테스트입니다.";

        when(postService.modify(any(),any(),any(),any()))
                .thenThrow(new AppException(ErrorCode.INVALID_PERMISSION,ErrorCode.INVALID_PERMISSION.getMessage()));

        mockMvc.perform(put("/api/v1/posts/1")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new PostRequest(title, body))))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("포스트 수정 실패3_데이터베이스 에러")
    @WithMockUser
    void post_modify_FAILED_db() throws Exception {
        String title = "테스트";
        String body = "테스트입니다.";

        when(postService.modify(any(),any(),any(),any()))
                .thenThrow(new AppException(ErrorCode.DATABASE_ERROR,ErrorCode.DATABASE_ERROR.getMessage()));

        mockMvc.perform(put("/api/v1/posts/1")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new PostRequest(title, body))))
                .andDo(print())
                .andExpect(status().isInternalServerError());
    }

포스트 수정 서비스 테스트 메서드 작성

@Test
    @DisplayName("포스트 수정 성공")
    void post_modify_SUCCESS() {
        when(postRepository.findById(any()))
                .thenReturn(Optional.of(post));
        when(userRepository.findByUserName(any()))
                .thenReturn(Optional.of(user));

        assertDoesNotThrow(() -> postService.modify(user.getUserName(), post.getId(), modifyPost.getTitle(), modifyPost.getBody()));
    }

    @Test
    @DisplayName("포스트 수정 실패1_포스트가 존재하지 않는 경우")
    void post_modify_FAILED_not_found_post() {
        when(postRepository.findById(any()))
                .thenReturn(Optional.empty());
        when(userRepository.findByUserName(any()))
                .thenReturn(Optional.of(user));

        AppException exception = assertThrows(AppException.class, () -> postService.modify(user.getUserName(), post.getId(), modifyPost.getTitle(), modifyPost.getBody()));
        assertEquals(ErrorCode.POST_NOT_FOUND, exception.getErrorCode());
    }

    @Test
    @DisplayName("포스트 수정 실패2_포스트 작성자와 유저가 다른 경우")
    void post_modify_FAILED_different() {
        when(postRepository.findById(any()))
                .thenReturn(Optional.of(post));
        when(userRepository.findByUserName(any()))
                .thenReturn(Optional.of(user2));

        AppException exception = assertThrows(AppException.class, () -> postService.modify(user.getUserName(), post.getId(), modifyPost.getTitle(), modifyPost.getBody()));
        assertEquals(ErrorCode.INVALID_PERMISSION, exception.getErrorCode());
    }

    @Test
    @DisplayName("포스트 수정 실패3_유저가 존재하지 않는 경우")
    void post_modify_FAILED_not_found_userName() {
        when(postRepository.findById(any()))
                .thenReturn(Optional.of(post));
        when(userRepository.findByUserName(any()))
                .thenReturn(Optional.empty());

        AppException exception = assertThrows(AppException.class, () -> postService.modify(user.getUserName(), post.getId(), modifyPost.getTitle(), modifyPost.getBody()));
        assertEquals(ErrorCode.INVALID_PERMISSION, exception.getErrorCode());
    }

포스트 수정 컨트롤러 구현

PostController 메서드 추가

@PutMapping("/{id}")
    public ResponseEntity<Response> modifyPost(@RequestBody PostRequest postRequest, @PathVariable Long id, Authentication authentication) {
        String userName = null;
        try {
            userName = authentication.getName();
        } catch (Exception e) {
            throw new AppException(ErrorCode.INVALID_TOKEN, ErrorCode.INVALID_TOKEN.getMessage());
        }
        PostResponse postResponse = postService.modify(userName, id, postRequest.getTitle(), postRequest.getBody());
        return ResponseEntity.ok().body(Response.of("SUCCESS", postResponse));
    }

PostDTO

  • PostRequest
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class PostRequest {
    private String title;
    private String body;
}
  • PostResponse
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class PostResponse {
    private String message;
    private Long postId;

    public static PostResponse of(String message, Long postId) {
        return PostResponse.builder()
                .message(message)
                .postId(postId)
                .build();
    }
}

포스트 수정 서비스 구현

public PostResponse modify(String userName, Long id, String title, String body) {
        //포스트 체크
        Post modifyPost = postRepository.findById(id).orElseThrow(() -> {
            throw new AppException(ErrorCode.POST_NOT_FOUND, ErrorCode.POST_NOT_FOUND.getMessage());
        });
        //유저 체크
        User user = userRepository.findByUserName(userName).orElseThrow(() -> {
            throw new AppException(ErrorCode.INVALID_PERMISSION, ErrorCode.INVALID_PERMISSION.getMessage());
        });
        //포스트 유저와 유처 비교
        if (!modifyPost.getUser().getUserName().equals(user.getUserName())) {
            throw new AppException(ErrorCode.INVALID_PERMISSION, ErrorCode.INVALID_PERMISSION.getMessage());
        }
        //포스트 수정
        modifyPost.setTitle(title);
        modifyPost.setBody(body);
        postRepository.saveAndFlush(modifyPost);
        //포스트 응답 DTO 변환 후 반환
        PostResponse postResponse = PostResponse.of("포스트 수정 완료", modifyPost.getId());
        return postResponse;
    }

🌉 회고

  • 아직 섬세하진 못하지만 TDD에 대해서 조금씩 익숙해지기 시작했다.
  • 하지만 시큐리티 부분은 제대로 테스트가 이루어지지 않는 점에서 보완이 필요할 것 같다.
profile
주니어 개발자를 향해서..

0개의 댓글