Skip to content

Commit d9835af

Browse files
authored
Merge pull request #64 from fullstack-dev-hub/refactor/#63-estimate-auth-principal
[Refactor] 견적서 API MemberPrincipalDto 적용
2 parents b6ee057 + 5f9ecd2 commit d9835af

File tree

7 files changed

+103
-127
lines changed

7 files changed

+103
-127
lines changed

backend/src/main/java/com/postdm/backend/domain/estimate/controller/EstimateController.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
import com.postdm.backend.domain.estimate.dto.EstimateRequestDto;
44
import com.postdm.backend.domain.estimate.dto.EstimateResponseDto;
55
import com.postdm.backend.domain.estimate.service.EstimateService;
6-
import com.postdm.backend.domain.member.domain.entity.Member;
6+
import com.postdm.backend.domain.member.dto.MemberPrincipalDto;
77
import com.postdm.backend.global.template.ResponseTemplate;
88
import io.swagger.v3.oas.annotations.Operation;
99
import io.swagger.v3.oas.annotations.tags.Tag;
10-
import jakarta.validation.Valid;
1110
import lombok.extern.slf4j.Slf4j;
1211
import org.springframework.http.HttpStatus;
1312
import org.springframework.http.ResponseEntity;
14-
import org.springframework.security.core.Authentication;
1513
import org.springframework.security.core.annotation.AuthenticationPrincipal;
16-
import org.springframework.security.core.context.SecurityContextHolder;
1714
import org.springframework.web.bind.annotation.*;
1815

1916
import java.util.List;
@@ -32,28 +29,32 @@ public EstimateController(EstimateService estimateService) {
3229

3330
@Operation(summary = "견적서 생성", description = "새로운 견적서를 생성합니다. 사용자는 content만 입력하며, title은 자동 생성됩니다.")
3431
@PostMapping
35-
public ResponseTemplate<EstimateResponseDto> createEstimate(@AuthenticationPrincipal Member currentUser,
36-
@RequestBody @Valid EstimateRequestDto requestDto) {
32+
public ResponseEntity<ResponseTemplate<EstimateResponseDto>> createEstimate(@AuthenticationPrincipal MemberPrincipalDto currentUser,
33+
@RequestBody EstimateRequestDto requestDto) {
3734

3835
EstimateResponseDto response = estimateService.createEstimate(requestDto.getContent(), currentUser);
3936

40-
return new ResponseTemplate<>(HttpStatus.CREATED, "견적서 생성 성공", response);
37+
return ResponseEntity
38+
.status(HttpStatus.CREATED)
39+
.body(new ResponseTemplate<>(HttpStatus.CREATED, "견적서 생성 성공", response));
4140
}
4241

4342
@Operation(summary = "견적서 조회", description = "관리자는 모든 견적서를, 사용자는 본인의 견적서만 조회합니다.")
4443
@GetMapping
45-
public ResponseTemplate<List<EstimateResponseDto>> getEstimates(@AuthenticationPrincipal Member currentUser) {
44+
public ResponseEntity<ResponseTemplate<List<EstimateResponseDto>>> getEstimates(@AuthenticationPrincipal MemberPrincipalDto currentUser) {
4645
List<EstimateResponseDto> response = estimateService.getEstimates(currentUser);
47-
return new ResponseTemplate<>(HttpStatus.OK, "견적서 조회 성공", response);
46+
return ResponseEntity
47+
.status(HttpStatus.OK)
48+
.body(new ResponseTemplate<>(HttpStatus.OK, "견적서 조회 성공", response));
4849
}
4950

5051
@Operation(summary = "견적서 상세 조회", description = "사용자의 특정 견적서를 상세 조회합니다.")
5152
@GetMapping("/{estimateId}")
52-
public ResponseTemplate<EstimateResponseDto> getEstimateDetail(@PathVariable Long estimateId) {
53-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
54-
Member member = (Member) authentication.getPrincipal();
55-
56-
EstimateResponseDto response = estimateService.getEstimateDetail(estimateId, member);
57-
return new ResponseTemplate<>(HttpStatus.OK, "견적서 상세 조회 성공", response);
53+
public ResponseEntity<ResponseTemplate<EstimateResponseDto>> getEstimateDetail(@PathVariable Long estimateId,
54+
@AuthenticationPrincipal MemberPrincipalDto principal) {
55+
EstimateResponseDto response = estimateService.getEstimateDetail(estimateId, principal);
56+
return ResponseEntity
57+
.status(HttpStatus.OK)
58+
.body(new ResponseTemplate<>(HttpStatus.OK, "견적서 상세 조회 성공", response));
5859
}
5960
}

backend/src/main/java/com/postdm/backend/domain/estimate/service/EstimateService.java

Lines changed: 30 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,104 +6,68 @@
66
import com.postdm.backend.domain.estimate.service.policy.AdminEstimatePolicy;
77
import com.postdm.backend.domain.estimate.service.policy.UserEstimatePolicy;
88
import com.postdm.backend.domain.member.domain.entity.Member;
9+
import com.postdm.backend.domain.member.domain.entity.MemberRole;
10+
import com.postdm.backend.domain.member.domain.repository.MemberRepository;
11+
import com.postdm.backend.domain.member.dto.MemberPrincipalDto;
912
import com.postdm.backend.global.common.exception.CustomException;
1013
import com.postdm.backend.global.common.response.ErrorCode;
14+
import lombok.RequiredArgsConstructor;
1115
import lombok.extern.slf4j.Slf4j;
12-
import org.springframework.security.core.Authentication;
13-
import org.springframework.security.core.context.SecurityContextHolder;
1416
import org.springframework.stereotype.Service;
1517

1618
import java.util.List;
1719
import java.util.stream.Collectors;
1820

19-
import static com.postdm.backend.domain.member.domain.entity.MemberRole.ADMIN;
20-
2121
@Slf4j
2222
@Service
23+
@RequiredArgsConstructor
2324
public class EstimateService {
2425
private final EstimateRepository estimateRepository;
26+
private final MemberRepository memberRepository;
2527
private final AdminEstimatePolicy adminPolicy;
2628
private final UserEstimatePolicy userPolicy;
2729

28-
public EstimateService(EstimateRepository estimateRepository,
29-
AdminEstimatePolicy adminPolicy,
30-
UserEstimatePolicy userPolicy) {
31-
this.estimateRepository = estimateRepository;
32-
this.adminPolicy = adminPolicy;
33-
this.userPolicy = userPolicy;
34-
}
35-
36-
public EstimateResponseDto createEstimate(String content, Member member) {
30+
public EstimateResponseDto createEstimate(String content, MemberPrincipalDto principal) {
3731
if (content == null || content.trim().isEmpty()) {
3832
throw new CustomException(ErrorCode.ESTIMATE_NULL);
3933
}
4034

41-
if (member == null) {
42-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
43-
if (authentication != null && authentication.getPrincipal() instanceof Member) {
44-
member = (Member) authentication.getPrincipal();
45-
} else {
46-
throw new CustomException(ErrorCode.AUTHORIZED_MEMBER_NOT_FOUND);
47-
}
48-
}
35+
Member member = findMemberOrThrow(principal.getId());
4936

5037
Estimate estimate = new Estimate(content, member);
51-
Estimate savedEstimate = estimateRepository.save(estimate);
38+
Estimate saved = estimateRepository.save(estimate);
5239

5340
return new EstimateResponseDto(
54-
savedEstimate.getId(),
55-
savedEstimate.getTitle(),
56-
savedEstimate.getContent(),
57-
savedEstimate.getCreatedAt(),
58-
savedEstimate.getMember().getId()
41+
saved.getId(),
42+
saved.getTitle(),
43+
saved.getContent(),
44+
saved.getCreatedAt(),
45+
saved.getMember().getId()
5946
);
6047
}
6148

62-
public List<EstimateResponseDto> getEstimates(Member member) {
63-
if (member == null) {
64-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
65-
if (authentication != null && authentication.getPrincipal() instanceof Member) {
66-
member = (Member) authentication.getPrincipal();
67-
} else {
68-
throw new CustomException(ErrorCode.AUTHORIZED_MEMBER_NOT_FOUND);
69-
}
70-
}
71-
72-
List<Estimate> estimates;
49+
public List<EstimateResponseDto> getEstimates(MemberPrincipalDto principal) {
50+
Member member = findMemberOrThrow(principal.getId());
7351

74-
if (member.getRole() == ADMIN) {
75-
estimates = adminPolicy.getEstimates(member, estimateRepository);
76-
} else {
77-
estimates = userPolicy.getEstimates(member, estimateRepository);
78-
}
52+
List<Estimate> estimates = (member.getRole() == MemberRole.ADMIN)
53+
? adminPolicy.getEstimates(member, estimateRepository)
54+
: userPolicy.getEstimates(member, estimateRepository);
7955

8056
return estimates.stream()
81-
.map(estimate -> new EstimateResponseDto(
82-
estimate.getId(),
83-
estimate.getTitle(),
84-
estimate.getContent(),
85-
estimate.getCreatedAt(),
86-
estimate.getMember().getId()
57+
.map(e -> new EstimateResponseDto(
58+
e.getId(), e.getTitle(), e.getContent(), e.getCreatedAt(), e.getMember().getId()
8759
))
8860
.collect(Collectors.toList());
8961
}
9062

91-
public EstimateResponseDto getEstimateDetail(Long estimateId, Member member) {
92-
if (member == null) {
93-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
94-
if (authentication != null && authentication.getPrincipal() instanceof Member) {
95-
member = (Member) authentication.getPrincipal();
96-
} else {
97-
throw new CustomException(ErrorCode.AUTHORIZED_MEMBER_NOT_FOUND);
98-
}
99-
}
63+
public EstimateResponseDto getEstimateDetail(Long estimateId, MemberPrincipalDto principal) {
64+
Member member = findMemberOrThrow(principal.getId());
10065

10166
Estimate estimate = estimateRepository.findById(estimateId)
10267
.orElseThrow(() -> new CustomException(ErrorCode.ESTIMATE_NOT_FOUND));
10368

104-
// 권한 체크
105-
if (member.getRole() != ADMIN &&
106-
estimate.getMember().getId() != member.getId()) {
69+
if (member.getRole() != MemberRole.ADMIN
70+
&& estimate.getMember().getId() != member.getId()) {
10771
throw new CustomException(ErrorCode.ACCESS_DENIED);
10872
}
10973

@@ -115,4 +79,9 @@ public EstimateResponseDto getEstimateDetail(Long estimateId, Member member) {
11579
estimate.getMember().getId()
11680
);
11781
}
82+
83+
private Member findMemberOrThrow(Long memberId) {
84+
return memberRepository.findById(memberId)
85+
.orElseThrow(() -> new CustomException(ErrorCode.AUTHORIZED_MEMBER_NOT_FOUND));
86+
}
11887
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.postdm.backend.domain.member.dto;
2+
3+
import com.postdm.backend.domain.member.domain.entity.Member;
4+
5+
public class TestPrincipalFactory {
6+
7+
public static MemberPrincipalDto create(Member member) {
8+
return new MemberPrincipalDto(
9+
member.getId(),
10+
member.getNickname(),
11+
member.getUsername()
12+
);
13+
}
14+
}

backend/src/test/java/com/postdm/backend/domain/estimate/controller/EstimateControllerTest.java

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,23 @@
44
import com.postdm.backend.domain.estimate.dto.EstimateRequestDto;
55
import com.postdm.backend.domain.estimate.entity.Estimate;
66
import com.postdm.backend.domain.estimate.entity.EstimateRepository;
7-
import com.postdm.backend.domain.estimate.service.EstimateService;
87
import com.postdm.backend.domain.member.domain.entity.Member;
98
import com.postdm.backend.domain.member.domain.entity.MemberRole;
109
import com.postdm.backend.domain.member.domain.repository.MemberRepository;
10+
import com.postdm.backend.domain.member.dto.MemberPrincipalDto;
11+
import com.postdm.backend.domain.member.dto.TestPrincipalFactory;
1112
import jakarta.transaction.Transactional;
1213
import org.junit.jupiter.api.BeforeEach;
1314
import org.junit.jupiter.api.DisplayName;
1415
import org.junit.jupiter.api.Test;
15-
import org.junit.jupiter.api.extension.ExtendWith;
16-
import org.mockito.InjectMocks;
17-
import org.mockito.Mock;
18-
import org.mockito.junit.jupiter.MockitoExtension;
1916
import org.springframework.beans.factory.annotation.Autowired;
2017
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
2118
import org.springframework.boot.test.context.SpringBootTest;
2219
import org.springframework.http.MediaType;
2320
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2421
import org.springframework.security.core.Authentication;
2522
import org.springframework.security.core.authority.SimpleGrantedAuthority;
26-
import org.springframework.security.core.context.SecurityContextHolder;
23+
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
2724
import org.springframework.test.context.ActiveProfiles;
2825
import org.springframework.test.web.servlet.MockMvc;
2926

@@ -36,7 +33,6 @@
3633
@AutoConfigureMockMvc
3734
@ActiveProfiles("test")
3835
@Transactional
39-
@ExtendWith(MockitoExtension.class)
4036
class EstimateControllerTest {
4137

4238
@Autowired
@@ -51,65 +47,52 @@ class EstimateControllerTest {
5147
@Autowired
5248
private EstimateRepository estimateRepository;
5349

54-
@Mock
55-
private EstimateService estimateService;
56-
57-
@InjectMocks
58-
private EstimateController estimateController;
59-
6050
private Member testMember;
6151
private Estimate testEstimate;
52+
private Authentication authentication;
6253

6354
@BeforeEach
6455
void setUp() {
6556
estimateRepository.deleteAll();
6657
memberRepository.deleteAll();
6758

68-
testMember = new Member(0L, "testUser", "test1", "password", "test@example.com", "01011111111", MemberRole.MEMBER);
59+
testMember = new Member(0L, "testUser", "nickname", "pass", "email@example.com", "01012345678", MemberRole.MEMBER);
6960
testMember = memberRepository.saveAndFlush(testMember);
7061

7162
testEstimate = new Estimate("테스트 견적 내용입니다.", testMember);
7263
estimateRepository.saveAndFlush(testEstimate);
7364

74-
Authentication auth = new UsernamePasswordAuthenticationToken(
75-
testMember, null,
65+
MemberPrincipalDto principal = TestPrincipalFactory.create(testMember);
66+
67+
authentication = new UsernamePasswordAuthenticationToken(
68+
principal, null,
7669
Collections.singletonList(new SimpleGrantedAuthority("ROLE_MEMBER"))
7770
);
78-
SecurityContextHolder.getContext().setAuthentication(auth);
7971
}
8072

8173
@Test
8274
@DisplayName("견적서 생성 API 테스트")
8375
void 견적서_생성_API_테스트() throws Exception {
84-
// Given
85-
String requestJson = objectMapper.writeValueAsString(new EstimateRequestDto("테스트 견적서 내용입니다."));
76+
String requestJson = objectMapper.writeValueAsString(
77+
new EstimateRequestDto("테스트 견적서 내용입니다.")
78+
);
8679

87-
// When & Then
8880
mockMvc.perform(post("/api/v1/estimates")
81+
.with(SecurityMockMvcRequestPostProcessors.authentication(authentication))
8982
.contentType(MediaType.APPLICATION_JSON)
9083
.content(requestJson))
91-
.andExpect(status().isOk())
84+
.andExpect(status().isCreated())
9285
.andExpect(jsonPath("$.data.content").value("테스트 견적서 내용입니다."));
9386
}
9487

9588
@Test
9689
@DisplayName("견적서 조회 API 테스트")
9790
void 견적서_조회_API_테스트() throws Exception {
98-
// When & Then
9991
mockMvc.perform(get("/api/v1/estimates")
92+
.with(SecurityMockMvcRequestPostProcessors.authentication(authentication))
10093
.contentType(MediaType.APPLICATION_JSON))
10194
.andExpect(status().isOk())
10295
.andExpect(jsonPath("$.data.size()").value(1))
10396
.andExpect(jsonPath("$.data[0].content").value("테스트 견적 내용입니다."));
10497
}
105-
106-
@Test
107-
@DisplayName("인증되지 않은 사용자가 견적서를 조회하면 403 응답을 반환해야 한다")
108-
void 인증되지_않은_사용자가_견적서를_조회하면_403_반환() throws Exception {
109-
SecurityContextHolder.clearContext();
110-
111-
mockMvc.perform(get("/api/v1/estimates")
112-
.contentType(MediaType.APPLICATION_JSON))
113-
.andExpect(status().isForbidden());
114-
}
11598
}

0 commit comments

Comments
 (0)