From b6bd92a3fbf46838d2bf2585ee9b78d3ce5cc789 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 30 Jul 2024 15:18:53 +0900 Subject: [PATCH 01/49] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/jwt/filter/JwtAuthenticationFilter.java | 10 +++++++++- .../domain/member/jwt/provider/JwtProvider.java | 11 ++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java index 376ccbf..3c82f73 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java @@ -7,6 +7,7 @@ import com.dnd.jjakkak.domain.member.repository.MemberRepository; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -71,7 +72,14 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse log.error("엑세스 토큰이 만료됨", e); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"Access Token expired\"}"); + response.getWriter().write("{\"error\": \"Token expired\"}"); + response.getWriter().flush(); + return; + } catch (MalformedJwtException e){ + log.error("손상된 토큰", e); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Malformed Token\"}"); response.getWriter().flush(); return; } diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java index 26bdaed..ef277a4 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java @@ -1,9 +1,6 @@ package com.dnd.jjakkak.domain.member.jwt.provider; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -91,7 +88,11 @@ public String validate(String jwt) { } catch (ExpiredJwtException e) { log.error("JWT 만료", e); return null; - } catch (Exception e) { + } catch (MalformedJwtException e) { + log.error("잘못된 토큰", e); + return null; + } + catch (Exception e) { log.error("JWT 검증 실패", e); return null; } From bee1768337d2d2c48eff1427edfd891ba6144bc4 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 30 Jul 2024 17:21:48 +0900 Subject: [PATCH 02/49] =?UTF-8?q?feat:=20MeetingMember=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/controller/MeetingController.java | 12 ++ .../dto/response/MeetingResponseDto.java | 19 ++- .../meeting/service/MeetingService.java | 20 ++- .../meetingmember/entity/MeetingMember.java | 47 +++++++ .../repository/MeetingMemberRepository.java | 15 +++ .../MeetingMemberRepositoryCustom.java | 31 +++++ .../MeetingMemberRepositoryImpl.java | 45 +++++++ .../member/controller/MemberController.java | 14 ++ .../dto/response/MemberResponseDto.java | 27 ++++ .../domain/member/service/MemberService.java | 20 +++ .../filter/JJwtAuthenticationFilterTest.java | 94 ------------- .../jwt/handler/OAuth2SuccessHandlerTest.java | 73 ---------- .../member/jwt/provider/JwtProviderTest.java | 126 ------------------ .../BlacklistedTokenRepositoryTest.java | 14 +- .../member/service/BlacklistServiceTest.java | 11 +- 15 files changed, 259 insertions(+), 309 deletions(-) create mode 100644 src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java create mode 100644 src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepository.java create mode 100644 src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryCustom.java create mode 100644 src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryImpl.java create mode 100644 src/main/java/com/dnd/jjakkak/domain/member/dto/response/MemberResponseDto.java delete mode 100644 src/test/java/com/dnd/jjakkak/domain/member/jwt/filter/JJwtAuthenticationFilterTest.java delete mode 100644 src/test/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandlerTest.java delete mode 100644 src/test/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProviderTest.java diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java index 6906c58..1fac3da 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java @@ -5,6 +5,7 @@ import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; +import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -59,6 +60,17 @@ public ResponseEntity getMeeting(@PathVariable("meetingId") return ResponseEntity.ok(meetingService.getMeeting(id)); } + /** + * 모임에 속한 회원 조회 + * + * @param id 조회할 모임 ID + * @return 200 (OK), body: 회원 응답 DTO + */ + @GetMapping("/{meetingId}/memberList") + public ResponseEntity> getMemberListByMemberId(@PathVariable("meetingId") Long id) { + return ResponseEntity.ok(meetingService.getMeetingListByMeetingId(id)); + } + /** * 모임을 수정하는 메서드입니다. * diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java index a2102c9..122f4ed 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java @@ -1,5 +1,6 @@ package com.dnd.jjakkak.domain.meeting.dto.response; +import com.dnd.jjakkak.domain.meeting.entity.Meeting; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -10,12 +11,12 @@ /** * 모임 응답 DTO 클래스입니다. * - * @author 정승조 - * @version 2024. 07. 25. + * @author 류태웅 + * @version 2024. 07. 30. */ @Getter -@AllArgsConstructor @Builder +@AllArgsConstructor public class MeetingResponseDto { private final Long meetingId; @@ -26,4 +27,16 @@ public class MeetingResponseDto { private final Boolean isOnline; private final Boolean isAnonymous; private final LocalDateTime voteEndDate; + + @Builder + public MeetingResponseDto(Meeting meeting){ + this.meetingId = meeting.getMeetingId(); + this.meetingName = meeting.getMeetingName(); + this.meetingStartDate = meeting.getMeetingStartDate(); + this.meetingEndDate = meeting.getMeetingEndDate(); + this.numberOfPeople = meeting.getNumberOfPeople(); + this.isOnline = meeting.getIsOnline(); + this.isAnonymous = meeting.getIsAnonymous(); + this.voteEndDate = meeting.getVoteEndDate(); + } } diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index 403db45..22be9f3 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -12,6 +12,9 @@ import com.dnd.jjakkak.domain.meeting.repository.MeetingRepository; import com.dnd.jjakkak.domain.meetingcategory.entity.MeetingCategory; import com.dnd.jjakkak.domain.meetingcategory.repository.MeetingCategoryRepository; +import com.dnd.jjakkak.domain.meetingmember.repository.MeetingMemberRepository; +import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; +import com.dnd.jjakkak.domain.member.entity.Member; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,8 +25,8 @@ /** * 모임 서비스 클래스입니다. * - * @author 정승조 - * @version 2024. 07. 25. + * @author 류태웅 + * @version 2024. 07. 30. */ @Service @RequiredArgsConstructor @@ -32,6 +35,7 @@ public class MeetingService { private final MeetingRepository meetingRepository; private final MeetingCategoryRepository meetingCategoryRepository; private final CategoryRepository categoryRepository; + private final MeetingMemberRepository meetingMemberRepository; @Transactional public void createMeeting(MeetingCreateRequestDto requestDto) { @@ -120,6 +124,18 @@ public MeetingResponseDto getMeeting(Long id) { .build(); } + /** + * 모임에 속한 회원 조회 + * + * @param id MeetingId + * @return List + */ + @Transactional(readOnly = true) + public List getMeetingListByMeetingId(Long id){ + List memberList = meetingMemberRepository.findByMeetingId(id); + return memberList.stream().map(MemberResponseDto::new).toList(); + } + /** * 모임을 수정하는 메서드입니다. * diff --git a/src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java b/src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java new file mode 100644 index 0000000..2af62cf --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java @@ -0,0 +1,47 @@ +package com.dnd.jjakkak.domain.meetingmember.entity; + +import com.dnd.jjakkak.domain.meeting.entity.Meeting; +import com.dnd.jjakkak.domain.member.entity.Member; +import jakarta.persistence.*; +import lombok.*; + +import java.io.Serializable; + +/** + * 회원 모임 엔티티 클래스입니다. + * + * @author 류태웅 + * @version 2024. 07. 30. + */ + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class MeetingMember { + @EmbeddedId + private Pk pk; + + @MapsId("meetingId") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "meeting_id") + private Meeting meeting; + + @MapsId("memberId") + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @Embeddable + @Getter + @NoArgsConstructor + @AllArgsConstructor + @EqualsAndHashCode + public static class Pk implements Serializable { + + @Column(name = "meeting_id") + private Long meetingId; + + @Column(name = "member_id") + private Long memberId; + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepository.java b/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepository.java new file mode 100644 index 0000000..2f0366d --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepository.java @@ -0,0 +1,15 @@ +package com.dnd.jjakkak.domain.meetingmember.repository; + +import com.dnd.jjakkak.domain.meetingmember.entity.MeetingMember; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * 회원 모임 JPA 레포지토리입니다. + * + * @author 류태웅 + * @version 2024. 07. 30. + */ + +public interface MeetingMemberRepository + extends JpaRepository, MeetingMemberRepositoryCustom { +} diff --git a/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryCustom.java b/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryCustom.java new file mode 100644 index 0000000..643a326 --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryCustom.java @@ -0,0 +1,31 @@ +package com.dnd.jjakkak.domain.meetingmember.repository; + +import com.dnd.jjakkak.domain.meeting.entity.Meeting; +import com.dnd.jjakkak.domain.member.entity.Member; +import org.springframework.data.repository.NoRepositoryBean; + +import java.util.List; + +/** + * 회원 모임 Querydsl 메서드 정의 인터페이스입니다. + * + * @author 류태웅 + * @version 2024. 07. 30. + */ + +@NoRepositoryBean +public interface MeetingMemberRepositoryCustom { + /** + * 회원 ID로 회원에 속한 모든 모임 찾기 + * + * @param memberId 회원 ID + */ + List findByMemberId(Long memberId); + + /** + * 모임 ID로 모임에 속한 모든 회원 찾기 + * + * @param meetingId 모임 ID + */ + List findByMeetingId(Long meetingId); +} diff --git a/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryImpl.java b/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryImpl.java new file mode 100644 index 0000000..05e4e34 --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/meetingmember/repository/MeetingMemberRepositoryImpl.java @@ -0,0 +1,45 @@ +package com.dnd.jjakkak.domain.meetingmember.repository; + +import com.dnd.jjakkak.domain.meeting.entity.Meeting; +import com.dnd.jjakkak.domain.meetingmember.entity.MeetingMember; +import com.dnd.jjakkak.domain.meetingmember.entity.QMeetingMember; +import com.dnd.jjakkak.domain.member.entity.Member; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; + +import java.util.List; + +/** + * 회원 모임 Querydsl 레포지토리 구현 클래스입니다. + * + * @author 류태웅 + * @version 2024. 07. 30. + */ + +public class MeetingMemberRepositoryImpl extends QuerydslRepositorySupport implements MeetingMemberRepositoryCustom{ + + public MeetingMemberRepositoryImpl() {super(MeetingMember.class);} + + /** + * {@inheritDoc} + */ + @Override + public List findByMemberId(Long memberId) { + QMeetingMember meetingMember = QMeetingMember.meetingMember; + return from(meetingMember) + .select(meetingMember.meeting) + .where(meetingMember.pk.memberId.eq(memberId)) + .fetch(); + } + + /** + * {@inheritDoc} + */ + @Override + public List findByMeetingId(Long meetingId) { + QMeetingMember meetingMember = QMeetingMember.meetingMember; + return from(meetingMember) + .select(meetingMember.member) + .where(meetingMember.pk.meetingId.eq(meetingId)) + .fetch(); + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/member/controller/MemberController.java b/src/main/java/com/dnd/jjakkak/domain/member/controller/MemberController.java index fb52cea..b3a3b90 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/controller/MemberController.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/controller/MemberController.java @@ -1,5 +1,6 @@ package com.dnd.jjakkak.domain.member.controller; +import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateNicknameRequestDto; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateProfileRequestDto; import com.dnd.jjakkak.domain.member.service.MemberService; @@ -8,6 +9,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + /** * Member의 CRUD에 사용하는 컨트롤러입니다. * @@ -21,6 +24,17 @@ public class MemberController { private final MemberService memberService; + /** + * 회원이 속한 모임 조회 + * + * @param id 조회할 멤버 ID + * @return 200 (OK), body: 모임 응답 DTO + */ + @GetMapping("/{memberId}/meetingList") + public ResponseEntity> getMemberListByMemberId(@PathVariable("memberId") Long id) { + return ResponseEntity.ok(memberService.getMeetingListByMemberId(id)); + } + @PatchMapping("/{memberId}/nickname") public ResponseEntity updateNickname( @PathVariable("memberId") Long id, diff --git a/src/main/java/com/dnd/jjakkak/domain/member/dto/response/MemberResponseDto.java b/src/main/java/com/dnd/jjakkak/domain/member/dto/response/MemberResponseDto.java new file mode 100644 index 0000000..5b60664 --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/member/dto/response/MemberResponseDto.java @@ -0,0 +1,27 @@ +package com.dnd.jjakkak.domain.member.dto.response; + +import com.dnd.jjakkak.domain.member.entity.Member; +import lombok.Builder; +import lombok.Getter; + +/** + * 회원 응답 DTO 클래스입니다. + * + * @author 류태웅 + * @version 2024. 07. 30. + */ +@Getter +public class MemberResponseDto { + private final long memberId; + private final String memberNickname; + private final long kakaoId; + private final String memberProfile; + + @Builder + public MemberResponseDto(Member member) { + this.memberId = member.getMemberId(); + this.memberNickname = member.getMemberNickname(); + this.kakaoId = member.getKakaoId(); + this.memberProfile = member.getMemberProfile(); + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/member/service/MemberService.java b/src/main/java/com/dnd/jjakkak/domain/member/service/MemberService.java index c1ea3c7..0eff2b4 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/service/MemberService.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/service/MemberService.java @@ -1,5 +1,8 @@ package com.dnd.jjakkak.domain.member.service; +import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; +import com.dnd.jjakkak.domain.meeting.entity.Meeting; +import com.dnd.jjakkak.domain.meetingmember.repository.MeetingMemberRepository; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateNicknameRequestDto; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateProfileRequestDto; import com.dnd.jjakkak.domain.member.entity.Member; @@ -10,6 +13,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + /** * Member의 CRUD Service입니다. * @@ -21,6 +26,19 @@ @RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; + private final MeetingMemberRepository meetingMemberRepository; + + /** + * 해당 회원이 속한 모임 출력 + * + * @param id MemberId + * @return List + */ + @Transactional(readOnly = true) + public List getMeetingListByMemberId(Long id){ + List meetingList = meetingMemberRepository.findByMemberId(id); + return meetingList.stream().map(MeetingResponseDto::new).toList(); + } /** * 닉네임 수정 @@ -66,4 +84,6 @@ public void deleteMember(Long id){ public void deletedMemberAllDeleted(){ memberRepository.deleteAllByIsDeleteTrue(); } + + } diff --git a/src/test/java/com/dnd/jjakkak/domain/member/jwt/filter/JJwtAuthenticationFilterTest.java b/src/test/java/com/dnd/jjakkak/domain/member/jwt/filter/JJwtAuthenticationFilterTest.java deleted file mode 100644 index a89beb2..0000000 --- a/src/test/java/com/dnd/jjakkak/domain/member/jwt/filter/JJwtAuthenticationFilterTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.dnd.jjakkak.domain.member.jwt.filter; - -import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.repository.MemberRepository; -import io.jsonwebtoken.ExpiredJwtException; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; - -import java.io.IOException; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -/** - * JWT 검증 필터 클래스입니다. - * - * @author 류태웅 - * @version 2024. 07. 27. - */ - -@ExtendWith(MockitoExtension.class) -class JwtAuthenticationFilterTest { - - @InjectMocks - JwtAuthenticationFilter jwtAuthenticationFilter; - - @Mock - JwtProvider jwtProvider; - - @Mock - MemberRepository memberRepository; - - private MockHttpServletRequest request; - private MockHttpServletResponse response; - - @BeforeEach - void setUp() { - request = new MockHttpServletRequest(); - response = new MockHttpServletResponse(); - SecurityContextHolder.clearContext(); - } - - @Test - @DisplayName("유효한 JWT로 인증 성공 테스트") - void testDoFilterInternal_ValidJwt() throws ServletException, IOException { - // given - String token = "valid_token"; - String kakaoId = "12345"; - Member member = Member.builder().kakaoId(12345L).memberNickname("test").build(); - - request.addHeader("Authorization", "Bearer " + token); - - when(jwtProvider.validate(token)).thenReturn(kakaoId); - when(memberRepository.findByKakaoId(12345L)).thenReturn(Optional.of(member)); - - // when - jwtAuthenticationFilter.doFilterInternal(request, response, Mockito.mock(FilterChain.class)); - - // then - assertThat(SecurityContextHolder.getContext().getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); - } - - @Test - @DisplayName("만료된 JWT로 인증 실패 테스트") - void testDoFilterInternal_ExpiredJwt() throws ServletException, IOException { - // given - String token = "expired_token"; - - request.addHeader("Authorization", "Bearer " + token); - - when(jwtProvider.validate(token)).thenThrow(new ExpiredJwtException(null, null, "JWT 만료")); - - // when - jwtAuthenticationFilter.doFilterInternal(request, response, Mockito.mock(FilterChain.class)); - - // then - assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } -} diff --git a/src/test/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandlerTest.java b/src/test/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandlerTest.java deleted file mode 100644 index 194f6a7..0000000 --- a/src/test/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandlerTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.dnd.jjakkak.domain.member.jwt.handler; - -import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.service.RefreshTokenService; -import jakarta.servlet.ServletException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.core.Authentication; - -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -/** - * 로그인/회원가입 성공 시 테스트 클래스입니다. - * - * @author 류태웅 - * @version 2024. 07. 27. - */ - -@ExtendWith(MockitoExtension.class) -class OAuth2SuccessHandlerTest { - - @InjectMocks - OAuth2SuccessHandler oAuth2SuccessHandler; - - @Mock - JwtProvider jwtProvider; - - @Mock - RefreshTokenService refreshTokenService; - - private MockHttpServletRequest request; - private MockHttpServletResponse response; - private Authentication authentication; - - @BeforeEach - void setUp() { - request = new MockHttpServletRequest(); - response = new MockHttpServletResponse(); - authentication = Mockito.mock(Authentication.class); - } - - @Test - @DisplayName("OAuth2 인증 성공 후 토큰 발급 테스트") - void testOnAuthenticationSuccess() throws IOException, ServletException, ServletException { - // given - Member member = Member.builder().kakaoId(12345L).memberNickname("test").build(); - String accessToken = "access_token"; - String refreshToken = "refresh_token"; - - when(authentication.getPrincipal()).thenReturn(member); - when(jwtProvider.createAccessToken("12345")).thenReturn(accessToken); - when(jwtProvider.createRefreshToken("12345")).thenReturn(refreshToken); - - // when - oAuth2SuccessHandler.onAuthenticationSuccess(request, response, authentication); - - // then - assertThat(response.getHeader("Authorization")).isEqualTo("Bearer " + accessToken); - assertThat(response.getHeader("RefreshToken")).isEqualTo(refreshToken); - } -} diff --git a/src/test/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProviderTest.java b/src/test/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProviderTest.java deleted file mode 100644 index 81ba742..0000000 --- a/src/test/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProviderTest.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.dnd.jjakkak.domain.member.jwt.provider; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.security.Keys; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; -import org.springframework.beans.factory.annotation.Value; - -import java.nio.charset.StandardCharsets; -import java.security.Key; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * JWT 생성 및 검증 테스트 클래스입니다. - * - * @author 류태웅 - * @version 2024. 07. 27. - */ - -class JwtProviderTest { - - private JwtProvider jwtProvider; - - @Value("${secret.key}") - private String secretKey; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - jwtProvider = new JwtProvider(secretKey); - } - - @Test - @DisplayName("JWT Access Token 생성 테스트") - void testCreateAccessToken() { - // given - String kakaoId = "12345"; - - // when - String token = jwtProvider.createAccessToken(kakaoId); - - // then - assertNotNull(token); - Claims claims = Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) - .build() - .parseClaimsJws(token) - .getBody(); - assertEquals(kakaoId, claims.getSubject()); - } - - @Test - @DisplayName("JWT Refresh Token 생성 테스트") - void testCreateRefreshToken() { - // given - String kakaoId = "12345"; - - // when - String token = jwtProvider.createRefreshToken(kakaoId); - - // then - assertNotNull(token); - Claims claims = Jwts.parserBuilder() - .setSigningKey(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) - .build() - .parseClaimsJws(token) - .getBody(); - assertEquals(kakaoId, claims.getSubject()); - } - - @Test - @DisplayName("JWT 유효성 검증 테스트") - void testValidate() { - // given - String kakaoId = "12345"; - String token = jwtProvider.createAccessToken(kakaoId); - - // when - String subject = jwtProvider.validate(token); - - // then - assertEquals(kakaoId, subject); - } - - @Test - @DisplayName("만료된 JWT 유효성 검증 테스트") - void testValidateExpiredToken() { - // given - String kakaoId = "12345"; - Key key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); - String token = Jwts.builder() - .signWith(key, SignatureAlgorithm.HS256) - .setSubject(kakaoId) - .setIssuedAt(new Date()) - .setExpiration(Date.from(Instant.now().minus(1, ChronoUnit.MINUTES))) - .compact(); - - // when - String subject = jwtProvider.validate(token); - - // then - assertNull(subject); - } - - @Test - @DisplayName("RefreshToken에서 subject 추출 테스트") - void testGetSubjectFromRefreshToken() { - // given - String kakaoId = "12345"; - String token = jwtProvider.createRefreshToken(kakaoId); - - // when - String subject = jwtProvider.getSubjectFromRefreshToken(token); - - // then - assertEquals(kakaoId, subject); - } -} diff --git a/src/test/java/com/dnd/jjakkak/domain/member/repository/BlacklistedTokenRepositoryTest.java b/src/test/java/com/dnd/jjakkak/domain/member/repository/BlacklistedTokenRepositoryTest.java index 1fcae56..9e72683 100644 --- a/src/test/java/com/dnd/jjakkak/domain/member/repository/BlacklistedTokenRepositoryTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/member/repository/BlacklistedTokenRepositoryTest.java @@ -26,9 +26,10 @@ class BlacklistedTokenRepositoryTest { @DisplayName("블랙리스트 토큰 저장 및 조회 테스트") void testSaveAndFindBlacklistedToken() { // given - BlacklistedToken token = new BlacklistedToken(); - token.setToken("test_token"); - token.setExpirationDate(LocalDateTime.now().plusDays(1)); + BlacklistedToken token = BlacklistedToken.builder() + .token("test_token") + .expirationDate(LocalDateTime.now().plusDays(1)) + .build(); blacklistedTokenRepository.save(token); // when @@ -43,9 +44,10 @@ void testSaveAndFindBlacklistedToken() { @DisplayName("만료된 블랙리스트 토큰 삭제 테스트") void testDeleteExpiredTokens() { // given - BlacklistedToken token = new BlacklistedToken(); - token.setToken("expired_token"); - token.setExpirationDate(LocalDateTime.now().minusDays(1)); + BlacklistedToken token = BlacklistedToken.builder() + .token("expired_token") + .expirationDate(LocalDateTime.now().minusDays(1)) + .build(); blacklistedTokenRepository.save(token); // when diff --git a/src/test/java/com/dnd/jjakkak/domain/member/service/BlacklistServiceTest.java b/src/test/java/com/dnd/jjakkak/domain/member/service/BlacklistServiceTest.java index 2c75851..1654f92 100644 --- a/src/test/java/com/dnd/jjakkak/domain/member/service/BlacklistServiceTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/member/service/BlacklistServiceTest.java @@ -34,8 +34,8 @@ class BlacklistServiceTest { void testIsTokenBlacklisted() { // given String token = "test_token"; - BlacklistedToken blacklistedToken = new BlacklistedToken(); - blacklistedToken.setToken(token); + BlacklistedToken blacklistedToken = BlacklistedToken.builder() + .token(token).build(); Mockito.when(blacklistedTokenRepository.findByToken(token)) .thenReturn(Optional.of(blacklistedToken)); @@ -52,9 +52,10 @@ void testBlacklistToken() { // given String token = "test_token"; LocalDateTime expirationDate = LocalDateTime.now().plusDays(1); - BlacklistedToken blacklistedToken = new BlacklistedToken(); - blacklistedToken.setToken(token); - blacklistedToken.setExpirationDate(expirationDate); + BlacklistedToken blacklistedToken = BlacklistedToken.builder() + .token(token) + .expirationDate(expirationDate) + .build(); // when blacklistService.blacklistToken(token, expirationDate); From 3e48e9ada59206ae74cda85789440ff509e9a374 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Wed, 31 Jul 2024 12:23:27 +0900 Subject: [PATCH 03/49] =?UTF-8?q?refactor:=20meetingResponseDto=EC=9D=98?= =?UTF-8?q?=20=EB=B9=8C=EB=8D=94=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/response/MeetingResponseDto.java | 3 --- .../meeting/service/MeetingService.java | 20 +++---------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java index 122f4ed..4bdcca6 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java @@ -1,7 +1,6 @@ package com.dnd.jjakkak.domain.meeting.dto.response; import com.dnd.jjakkak.domain.meeting.entity.Meeting; -import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -15,8 +14,6 @@ * @version 2024. 07. 30. */ @Getter -@Builder -@AllArgsConstructor public class MeetingResponseDto { private final Long meetingId; diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index 22be9f3..ae24036 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -84,15 +84,8 @@ public List getMeetingList() { List meetingList = meetingRepository.findAll(); for (Meeting meeting : meetingList) { MeetingResponseDto meetingResponseDto = MeetingResponseDto.builder() - .meetingId(meeting.getMeetingId()) - .meetingName(meeting.getMeetingName()) - .meetingStartDate(meeting.getMeetingStartDate()) - .meetingEndDate(meeting.getMeetingEndDate()) - .numberOfPeople(meeting.getNumberOfPeople()) - .isOnline(meeting.getIsOnline()) - .isAnonymous(meeting.getIsAnonymous()) - .voteEndDate(meeting.getVoteEndDate()) - .build(); + .meeting(meeting) + .build(); meetingResponseDtoList.add(meetingResponseDto); } @@ -113,14 +106,7 @@ public MeetingResponseDto getMeeting(Long id) { .orElseThrow(MeetingNotFoundException::new); return MeetingResponseDto.builder() - .meetingId(meeting.getMeetingId()) - .meetingName(meeting.getMeetingName()) - .meetingStartDate(meeting.getMeetingStartDate()) - .meetingEndDate(meeting.getMeetingEndDate()) - .numberOfPeople(meeting.getNumberOfPeople()) - .isOnline(meeting.getIsOnline()) - .isAnonymous(meeting.getIsAnonymous()) - .voteEndDate(meeting.getVoteEndDate()) + .meeting(meeting) .build(); } From 4562843b4667c2d02590744812c6cd29ee65600b Mon Sep 17 00:00:00 2001 From: seungjo Date: Wed, 31 Jul 2024 17:30:16 +0900 Subject: [PATCH 04/49] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jjakkak/domain/meeting/MeetingDummy.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java index 2c72503..fc38340 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java @@ -56,16 +56,21 @@ public static MeetingCreateRequestDto createInvalidRequestDto() { */ public static MeetingResponseDto createResponseDto() { - return new MeetingResponseDto( - 1L, - "세븐일레븐", - LocalDate.of(2024, 7, 27), - LocalDate.of(2024, 7, 29), - 6, - true, - false, - LocalDateTime.of(2024, 7, 26, 23, 59, 59) - ); + Meeting meeting = Meeting.builder() + .meetingName("세븐일레븐") + .meetingStartDate(LocalDate.of(2024, 7, 27)) + .meetingEndDate(LocalDate.of(2024, 7, 29)) + .numberOfPeople(6) + .isOnline(true) + .isAnonymous(false) + .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) + .build(); + + ReflectionTestUtils.setField(meeting, "meetingId", 1L); + + return MeetingResponseDto.builder() + .meeting(meeting) + .build(); } /** From 9c4d957163c56dea47136efe13cf8cecdbe1dd11 Mon Sep 17 00:00:00 2001 From: seungjo Date: Thu, 1 Aug 2024 17:20:57 +0900 Subject: [PATCH 05/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20-=20?= =?UTF-8?q?=EB=A6=AC=EB=8D=94,=20UUID=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java index b5516da..df48d00 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java @@ -50,6 +50,12 @@ public class Meeting { @Column(name = "confirmed_schedule") private LocalDateTime confirmedSchedule; + @Column(nullable = false, name = "meeting_leader_id") + private Long meetingLeaderId; + + @Column(nullable = false, name = "meeting_uuid", length = 8) + private String meetingUuid; + @Builder public Meeting(String meetingName, LocalDate meetingStartDate, LocalDate meetingEndDate, Integer numberOfPeople, Boolean isOnline, Boolean isAnonymous, LocalDateTime voteEndDate) { From d664adf5d1642cf10732908a52332754b83d12af Mon Sep 17 00:00:00 2001 From: seungjo Date: Thu, 1 Aug 2024 17:21:34 +0900 Subject: [PATCH 06/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20-=20?= =?UTF-8?q?=EB=A6=AC=EB=8D=94,=20UUID=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java index df48d00..c62f8d0 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java @@ -58,7 +58,8 @@ public class Meeting { @Builder public Meeting(String meetingName, LocalDate meetingStartDate, LocalDate meetingEndDate, - Integer numberOfPeople, Boolean isOnline, Boolean isAnonymous, LocalDateTime voteEndDate) { + Integer numberOfPeople, Boolean isOnline, Boolean isAnonymous, + LocalDateTime voteEndDate, Long meetingLeaderId, String meetingUuid) { this.meetingName = meetingName; this.meetingStartDate = meetingStartDate; this.meetingEndDate = meetingEndDate; @@ -66,6 +67,8 @@ public Meeting(String meetingName, LocalDate meetingStartDate, LocalDate meeting this.isOnline = isOnline; this.isAnonymous = isAnonymous; this.voteEndDate = voteEndDate; + this.meetingLeaderId = meetingLeaderId; + this.meetingUuid = meetingUuid; } From de50b9c8c878f2172f42e099c9d7a862ad6e686d Mon Sep 17 00:00:00 2001 From: seungjo Date: Thu, 1 Aug 2024 17:30:59 +0900 Subject: [PATCH 07/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20-=20UUID?= =?UTF-8?q?=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java index c62f8d0..6b9ebfd 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java @@ -16,6 +16,9 @@ * @version 2024. 07. 23. */ @Entity +@Table(indexes = { + @Index(name = "idx_meeting_uuid", columnList = "meeting_uuid", unique = true) +}) @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @ToString From dd428108ff3a37a2f044836ad0d11bbce2025345 Mon Sep 17 00:00:00 2001 From: seungjo Date: Thu, 1 Aug 2024 17:31:32 +0900 Subject: [PATCH 08/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20-=20=EA=B6=8C=ED=95=9C=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC,=20=EC=9D=91=EB=8B=B5=20UUID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/controller/MeetingController.java | 19 ++++-- .../response/MeetingCreateResponseDto.java | 21 +++++++ .../meeting/repository/MeetingRepository.java | 8 +++ .../meeting/service/MeetingService.java | 59 +++++++++++++++++-- 4 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingCreateResponseDto.java diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java index 1fac3da..b91e3f7 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java @@ -3,6 +3,7 @@ import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; +import com.dnd.jjakkak.domain.meeting.dto.response.MeetingCreateResponseDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; @@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Objects; /** * 모임 컨트롤러 클래스입니다. @@ -27,16 +29,25 @@ public class MeetingController { private final MeetingService meetingService; + /** * 모임을 생성하는 메서드입니다. * + * @param token JWT Token (Bearer Token) * @param requestDto 모임 생성 요청 DTO - * @return 201 (CREATED) + * @return 201 (CREATED), body: 모임 생성 응답 DTO (UUID) */ @PostMapping - public ResponseEntity createGroup(@Valid @RequestBody MeetingCreateRequestDto requestDto) { - meetingService.createMeeting(requestDto); - return ResponseEntity.status(HttpStatus.CREATED).build(); + public ResponseEntity createGroup(@RequestHeader("Authorization") String token, + @Valid @RequestBody MeetingCreateRequestDto requestDto) { + + if (Objects.isNull(token)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + return ResponseEntity + .status(HttpStatus.CREATED) + .body(meetingService.createMeeting(token, requestDto)); } /** diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingCreateResponseDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingCreateResponseDto.java new file mode 100644 index 0000000..65a9a84 --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingCreateResponseDto.java @@ -0,0 +1,21 @@ +package com.dnd.jjakkak.domain.meeting.dto.response; + +import lombok.Builder; +import lombok.Getter; + +/** + * 모임이 생성된 후 UUID를 반환하는 응답 DTO 클래스입니다. + * + * @author 정승조 + * @version 2024. 08. 01. + */ +@Getter +public class MeetingCreateResponseDto { + + private final String meetingUuid; + + @Builder + public MeetingCreateResponseDto(String meetingUuid) { + this.meetingUuid = meetingUuid; + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java b/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java index 986051f..971ba8f 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java @@ -10,4 +10,12 @@ * @version 2024. 07. 25. */ public interface MeetingRepository extends JpaRepository { + + /** + * 모임 uuid로 모임이 존재하는지 확인합니다. + * + * @param uuid 모임 uuid + * @return 모임이 존재하는지 여부 + */ + boolean existsByMeetingUuid(String uuid); } diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index ae24036..d697f15 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -6,6 +6,7 @@ import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; +import com.dnd.jjakkak.domain.meeting.dto.response.MeetingCreateResponseDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.entity.Meeting; import com.dnd.jjakkak.domain.meeting.exception.MeetingNotFoundException; @@ -15,12 +16,17 @@ import com.dnd.jjakkak.domain.meetingmember.repository.MeetingMemberRepository; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; import com.dnd.jjakkak.domain.member.entity.Member; +import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; +import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.UUID; /** * 모임 서비스 클래스입니다. @@ -36,13 +42,25 @@ public class MeetingService { private final MeetingCategoryRepository meetingCategoryRepository; private final CategoryRepository categoryRepository; private final MeetingMemberRepository meetingMemberRepository; + private final JwtProvider jwtProvider; + private final MemberRepository memberRepository; + /** + * 모임을 생성하는 메서드입니다. + * + * @param token JWT Token (Bearer Token) + * @param requestDto 모임 생성 요청 DTO + * @return 모임 생성 응답 DTO (UUID) + */ @Transactional - public void createMeeting(MeetingCreateRequestDto requestDto) { + public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequestDto requestDto) { // checkMeetingDate 메서드를 호출하여 유효성 검사를 진행합니다. requestDto.checkMeetingDate(); + Member member = getMemberByToken(token); + String uuid = generateUuid(); + // 모임 생성 로직 Meeting meeting = Meeting.builder() .meetingName(requestDto.getMeetingName()) @@ -52,6 +70,8 @@ public void createMeeting(MeetingCreateRequestDto requestDto) { .isOnline(requestDto.getIsOnline()) .isAnonymous(requestDto.getIsAnonymous()) .voteEndDate(requestDto.getVoteEndDate()) + .meetingLeaderId(member.getMemberId()) + .meetingUuid(uuid) .build(); meetingRepository.save(meeting); @@ -70,6 +90,10 @@ public void createMeeting(MeetingCreateRequestDto requestDto) { meetingCategoryRepository.save(meetingCategory); } + + return MeetingCreateResponseDto.builder() + .meetingUuid(uuid) + .build(); } /** @@ -84,8 +108,8 @@ public List getMeetingList() { List meetingList = meetingRepository.findAll(); for (Meeting meeting : meetingList) { MeetingResponseDto meetingResponseDto = MeetingResponseDto.builder() - .meeting(meeting) - .build(); + .meeting(meeting) + .build(); meetingResponseDtoList.add(meetingResponseDto); } @@ -117,7 +141,7 @@ public MeetingResponseDto getMeeting(Long id) { * @return List */ @Transactional(readOnly = true) - public List getMeetingListByMeetingId(Long id){ + public List getMeetingListByMeetingId(Long id) { List memberList = meetingMemberRepository.findByMeetingId(id); return memberList.stream().map(MemberResponseDto::new).toList(); } @@ -191,4 +215,31 @@ public void deleteMeeting(Long id) { meetingRepository.deleteById(id); } + + /** + * UUID를 생성하는 메서드입니다. + * + * @return UUID + */ + private String generateUuid() { + String uuid; + + do { + uuid = UUID.randomUUID().toString().substring(0, 8); + } while (meetingRepository.existsByMeetingUuid(uuid)); + + return uuid; + } + + /** + * 토큰을 통해 회원 정보를 조회하는 메서드입니다. + * + * @param token AccessToken + * @return Member Entity + */ + private Member getMemberByToken(String token) { + String kakaoId = Objects.requireNonNull(jwtProvider.validate(token)); + return memberRepository.findByKakaoId(Long.parseLong(kakaoId)) + .orElseThrow(MemberNotFoundException::new); + } } From bf20bf46c8c961dc6f7f3d0dad03186bae60314a Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Fri, 2 Aug 2024 14:37:57 +0900 Subject: [PATCH 09/49] =?UTF-8?q?refactor:=20OAuth2SuccessHandler=20Token?= =?UTF-8?q?=20=EC=A0=84=EC=86=A1=20=EB=B0=A9=EC=8B=9D=20=EC=BF=A0=ED=82=A4?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/handler/OAuth2SuccessHandler.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java index 6beff03..4b2d300 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java @@ -4,6 +4,7 @@ import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -52,9 +53,19 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String refreshToken = jwtProvider.createRefreshToken(kakaoId); refreshTokenService.createRefreshToken(oauth2User.getMemberId(), refreshToken); - // 토큰을 응답 헤더에 추가 - response.setHeader("Authorization", "Bearer " + token); - response.setHeader("RefreshToken", refreshToken); + Cookie accessTokenCookie = new Cookie("access_token", token); + accessTokenCookie.setSecure(true); + accessTokenCookie.setHttpOnly(true); + accessTokenCookie.setMaxAge(60*30); // 만료 시간 : 30분 + accessTokenCookie.setPath("/"); // 모든 경로에서 접근 가능하도록 설정 (초 단위) + response.addCookie(accessTokenCookie); + + Cookie refreshTokenCookie = new Cookie("refresh_token", refreshToken); + refreshTokenCookie.setSecure(true); + refreshTokenCookie.setHttpOnly(true); + refreshTokenCookie.setMaxAge(60*60*24*7); // 만료 시간 : 1wn + refreshTokenCookie.setPath("/"); // 모든 경로에서 접근 가능하도록 설정 (초 단위) + response.addCookie(refreshTokenCookie); // 로그인 성공 시 리다이렉트되는 URL은 추후 수정 필요 response.sendRedirect("http://localhost:8080/auth/oauth-response/"); From 3838e05701262c11ee01be7d18fe13c71902a7d8 Mon Sep 17 00:00:00 2001 From: seungjo Date: Fri, 2 Aug 2024 15:12:46 +0900 Subject: [PATCH 10/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20-=20UUID?= =?UTF-8?q?=20=EB=B0=8F=20Token=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/controller/MeetingController.java | 53 +++----- .../dto/response/MeetingResponseDto.java | 10 +- .../MeetingUnauthorizedException.java | 23 ++++ .../meeting/repository/MeetingRepository.java | 10 ++ .../meeting/service/MeetingService.java | 117 +++++------------- .../meetingmember/entity/MeetingMember.java | 7 ++ 6 files changed, 96 insertions(+), 124 deletions(-) create mode 100644 src/main/java/com/dnd/jjakkak/domain/meeting/exception/MeetingUnauthorizedException.java diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java index b91e3f7..5ad254f 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java @@ -2,11 +2,11 @@ import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; -import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingCreateResponseDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; +import jakarta.servlet.http.Cookie; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -33,42 +33,32 @@ public class MeetingController { /** * 모임을 생성하는 메서드입니다. * - * @param token JWT Token (Bearer Token) - * @param requestDto 모임 생성 요청 DTO + * @param accessToken JWT Token (Access Token) + * @param requestDto 모임 생성 요청 DTO * @return 201 (CREATED), body: 모임 생성 응답 DTO (UUID) */ @PostMapping - public ResponseEntity createGroup(@RequestHeader("Authorization") String token, + public ResponseEntity createGroup(@CookieValue("access_token") Cookie accessToken, @Valid @RequestBody MeetingCreateRequestDto requestDto) { - if (Objects.isNull(token)) { + if (Objects.isNull(accessToken)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } return ResponseEntity .status(HttpStatus.CREATED) - .body(meetingService.createMeeting(token, requestDto)); + .body(meetingService.createMeeting(accessToken.getValue(), requestDto)); } /** - * 전체 모임을 조회하는 메서드입니다. + * 모임의 UUID로 모임을 조회하는 메서드입니다. * - * @return 200 (OK), body: 모임 응답 DTO 리스트 - */ - @GetMapping - public ResponseEntity> getMeetingList() { - return ResponseEntity.ok(meetingService.getMeetingList()); - } - - /** - * 특정 모임을 조회하는 메서드입니다. - * - * @param id 조회할 모임 ID + * @param uuid 조회할 모임 UUID * @return 200 (OK), body: 모임 응답 DTO */ - @GetMapping("/{meetingId}") - public ResponseEntity getMeeting(@PathVariable("meetingId") Long id) { - return ResponseEntity.ok(meetingService.getMeeting(id)); + @GetMapping("/{meetingUuid}") + public ResponseEntity getMeetingByUuid(@PathVariable("meetingUuid") String uuid) { + return ResponseEntity.ok(meetingService.getMeetingByUuid(uuid)); } /** @@ -82,21 +72,6 @@ public ResponseEntity> getMemberListByMemberId(@PathVari return ResponseEntity.ok(meetingService.getMeetingListByMeetingId(id)); } - /** - * 모임을 수정하는 메서드입니다. - * - * @param id 모임 ID - * @param requestDto 수정된 모임 정보 DTO - * @return 200 (OK) - */ - @PatchMapping("/{meetingId}") - public ResponseEntity updateMeeting(@PathVariable("meetingId") Long id, - @Valid @RequestBody MeetingUpdateRequestDto requestDto) { - - meetingService.updateMeeting(id, requestDto); - return ResponseEntity.ok().build(); - } - /** * 모임의 확정된 일자를 수정하는 메서드입니다. * @@ -118,8 +93,10 @@ public ResponseEntity confirmMeeting(@PathVariable("meetingId") Long id, * @return 200 (OK) */ @DeleteMapping("/{meetingId}") - public ResponseEntity deleteMeeting(@PathVariable("meetingId") Long id) { - meetingService.deleteMeeting(id); + public ResponseEntity deleteMeeting(@CookieValue("access_token") Cookie accessToken, + @PathVariable("meetingId") Long id) { + + meetingService.deleteMeeting(accessToken.getValue(), id); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java index 4bdcca6..567320b 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java @@ -10,7 +10,7 @@ /** * 모임 응답 DTO 클래스입니다. * - * @author 류태웅 + * @author 정승조, 류태웅 * @version 2024. 07. 30. */ @Getter @@ -24,9 +24,12 @@ public class MeetingResponseDto { private final Boolean isOnline; private final Boolean isAnonymous; private final LocalDateTime voteEndDate; + private final LocalDateTime confirmedSchedule; + private final Long meetingLeaderId; + private final String meetingUuid; @Builder - public MeetingResponseDto(Meeting meeting){ + public MeetingResponseDto(Meeting meeting) { this.meetingId = meeting.getMeetingId(); this.meetingName = meeting.getMeetingName(); this.meetingStartDate = meeting.getMeetingStartDate(); @@ -35,5 +38,8 @@ public MeetingResponseDto(Meeting meeting){ this.isOnline = meeting.getIsOnline(); this.isAnonymous = meeting.getIsAnonymous(); this.voteEndDate = meeting.getVoteEndDate(); + this.confirmedSchedule = meeting.getConfirmedSchedule(); + this.meetingLeaderId = meeting.getMeetingLeaderId(); + this.meetingUuid = meeting.getMeetingUuid(); } } diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/exception/MeetingUnauthorizedException.java b/src/main/java/com/dnd/jjakkak/domain/meeting/exception/MeetingUnauthorizedException.java new file mode 100644 index 0000000..ae98807 --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/exception/MeetingUnauthorizedException.java @@ -0,0 +1,23 @@ +package com.dnd.jjakkak.domain.meeting.exception; + +import com.dnd.jjakkak.global.exception.GeneralException; + +/** + * 모임에 대한 권한이 없을 때 발생하는 예외입니다. + * + * @author 정승조 + * @version 2024. 08. 01. + */ +public class MeetingUnauthorizedException extends GeneralException { + + private static final String MESSAGE = "모임에 대한 권한이 없습니다."; + + public MeetingUnauthorizedException() { + super(MESSAGE); + } + + @Override + public int getStatusCode() { + return 401; + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java b/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java index 971ba8f..76cafe7 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/repository/MeetingRepository.java @@ -3,6 +3,8 @@ import com.dnd.jjakkak.domain.meeting.entity.Meeting; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + /** * 모임 JPA 레포지토리입니다. * @@ -18,4 +20,12 @@ public interface MeetingRepository extends JpaRepository { * @return 모임이 존재하는지 여부 */ boolean existsByMeetingUuid(String uuid); + + /** + * 모임 uuid로 모임을 조회합니다. + * + * @param uuid 모임 uuid + * @return 모임 + */ + Optional findByMeetingUuid(String uuid); } diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index d697f15..d88a0fa 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -5,11 +5,11 @@ import com.dnd.jjakkak.domain.category.repository.CategoryRepository; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; -import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingCreateResponseDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.entity.Meeting; import com.dnd.jjakkak.domain.meeting.exception.MeetingNotFoundException; +import com.dnd.jjakkak.domain.meeting.exception.MeetingUnauthorizedException; import com.dnd.jjakkak.domain.meeting.repository.MeetingRepository; import com.dnd.jjakkak.domain.meetingcategory.entity.MeetingCategory; import com.dnd.jjakkak.domain.meetingcategory.repository.MeetingCategoryRepository; @@ -23,7 +23,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -48,7 +47,7 @@ public class MeetingService { /** * 모임을 생성하는 메서드입니다. * - * @param token JWT Token (Bearer Token) + * @param token JWT Token * @param requestDto 모임 생성 요청 DTO * @return 모임 생성 응답 DTO (UUID) */ @@ -96,44 +95,6 @@ public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequest .build(); } - /** - * 전체 모임 목록을 조회하는 메서드입니다. - * - * @return 모임 목록 - */ - @Transactional(readOnly = true) - public List getMeetingList() { - - List meetingResponseDtoList = new ArrayList<>(); - List meetingList = meetingRepository.findAll(); - for (Meeting meeting : meetingList) { - MeetingResponseDto meetingResponseDto = MeetingResponseDto.builder() - .meeting(meeting) - .build(); - - meetingResponseDtoList.add(meetingResponseDto); - } - - return meetingResponseDtoList; - } - - /** - * 모임을 조회하는 메서드입니다. - * - * @param id 조회할 모임 ID - * @return 모임 응답 DTO - */ - @Transactional(readOnly = true) - public MeetingResponseDto getMeeting(Long id) { - - Meeting meeting = meetingRepository.findById(id) - .orElseThrow(MeetingNotFoundException::new); - - return MeetingResponseDto.builder() - .meeting(meeting) - .build(); - } - /** * 모임에 속한 회원 조회 * @@ -143,77 +104,64 @@ public MeetingResponseDto getMeeting(Long id) { @Transactional(readOnly = true) public List getMeetingListByMeetingId(Long id) { List memberList = meetingMemberRepository.findByMeetingId(id); - return memberList.stream().map(MemberResponseDto::new).toList(); + return memberList.stream() + .map(MemberResponseDto::new) + .toList(); } /** - * 모임을 수정하는 메서드입니다. + * 모임의 확정된 일자를 설정하는 메서드입니다. * * @param id 모임 ID - * @param requestDto 수정된 모임 정보 DTO + * @param requestDto 모임 확정 요청 DTO */ @Transactional - public void updateMeeting(Long id, MeetingUpdateRequestDto requestDto) { + public void confirmMeeting(Long id, MeetingConfirmRequestDto requestDto) { Meeting meeting = meetingRepository.findById(id) .orElseThrow(MeetingNotFoundException::new); - // checkMeetingDate 메서드를 호출하여 유효성 검사를 진행합니다. - requestDto.checkMeetingDate(); - - // dirty checking - 모임 정보를 수정합니다. - meeting.updateMeeting(requestDto); - - // 모임의 카테고리 값도 수정 필요 - // 기존 모임과 연결된 카테고리 정보를 삭제합니다. - meetingCategoryRepository.deleteByMeetingId(meeting.getMeetingId()); - - for (Long categoryId : requestDto.getCategoryIds()) { - Category category = categoryRepository.findById(categoryId) - .orElseThrow(CategoryNotFoundException::new); - - MeetingCategory.Pk pk = new MeetingCategory.Pk(meeting.getMeetingId(), category.getCategoryId()); - MeetingCategory meetingCategory = MeetingCategory.builder() - .pk(pk) - .meeting(meeting) - .category(category) - .build(); - - meetingCategoryRepository.save(meetingCategory); - } + meeting.updateConfirmedSchedule(requestDto); } + /** - * 모임의 확정된 일자를 설정하는 메서드입니다. + * 모임을 삭제하는 메서드입니다. * - * @param id 모임 ID - * @param requestDto 모임 확정 요청 DTO + * @param token JWT Token + * @param id 모임 ID */ @Transactional - public void confirmMeeting(Long id, MeetingConfirmRequestDto requestDto) { + public void deleteMeeting(String token, Long id) { + + Member member = getMemberByToken(token); Meeting meeting = meetingRepository.findById(id) .orElseThrow(MeetingNotFoundException::new); - meeting.updateConfirmedSchedule(requestDto); + if (!meeting.getMeetingLeaderId().equals(member.getMemberId())) { + throw new MeetingUnauthorizedException(); + } + + meetingRepository.deleteById(id); + meetingCategoryRepository.deleteByMeetingId(id); } /** - * 모임을 삭제하는 메서드입니다. + * UUID로 모임을 조회하는 메서드입니다. * - * @param id 삭제할 모임 ID + * @param uuid 조회할 모임 UUID + * @return 모임 응답 DTO */ - @Transactional - public void deleteMeeting(Long id) { - - // TODO 1: 모임을 생성한 리더가 맞는지 검증 확인 필요 + @Transactional(readOnly = true) + public MeetingResponseDto getMeetingByUuid(String uuid) { - // TODO 2: 모임과 엮인 모임 일정 테이블 삭제 로직 추가 필요 - if (!meetingRepository.existsById(id)) { - throw new MeetingNotFoundException(); - } + Meeting meeting = meetingRepository.findByMeetingUuid(uuid) + .orElseThrow(MeetingNotFoundException::new); - meetingRepository.deleteById(id); + return MeetingResponseDto.builder() + .meeting(meeting) + .build(); } /** @@ -242,4 +190,5 @@ private Member getMemberByToken(String token) { return memberRepository.findByKakaoId(Long.parseLong(kakaoId)) .orElseThrow(MemberNotFoundException::new); } + } diff --git a/src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java b/src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java index 2af62cf..af5610f 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java +++ b/src/main/java/com/dnd/jjakkak/domain/meetingmember/entity/MeetingMember.java @@ -31,6 +31,13 @@ public class MeetingMember { @JoinColumn(name = "member_id") private Member member; + @Builder + public MeetingMember(Pk pk, Meeting meeting, Member member) { + this.pk = pk; + this.meeting = meeting; + this.member = member; + } + @Embeddable @Getter @NoArgsConstructor From 6fa88f77e5faee97742257f8947ab7c22c70e62b Mon Sep 17 00:00:00 2001 From: seungjo Date: Fri, 2 Aug 2024 16:05:19 +0900 Subject: [PATCH 11/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=ED=9B=84?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MeetingControllerTest.java | 392 ------------------ 1 file changed, 392 deletions(-) delete mode 100644 src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java deleted file mode 100644 index c1b1833..0000000 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java +++ /dev/null @@ -1,392 +0,0 @@ -package com.dnd.jjakkak.domain.meeting.controller; - -import com.dnd.jjakkak.domain.category.entity.Category; -import com.dnd.jjakkak.domain.category.repository.CategoryRepository; -import com.dnd.jjakkak.domain.meeting.MeetingDummy; -import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; -import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; -import com.dnd.jjakkak.domain.meeting.entity.Meeting; -import com.dnd.jjakkak.domain.meeting.repository.MeetingRepository; -import com.dnd.jjakkak.domain.meeting.service.MeetingService; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MockMvc; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.*; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.restdocs.snippet.Attributes.key; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * 모임 컨트롤러 테스트 클래스입니다. - * - * @author 정승조 - * @version 2024. 07. 25. - */ -@ActiveProfiles("test") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) -@AutoConfigureMockMvc -class MeetingControllerTest { - - @Autowired - MockMvc mockMvc; - - @Autowired - MeetingService groupService; - - @Autowired - MeetingRepository meetingRepository; - - @Autowired - CategoryRepository categoryRepository; - - @Autowired - ObjectMapper objectMapper; - - Category school, friend, teamProject, session, study, hobby, volunteer, etc; - - - @BeforeEach - void setUp() { - - // 1.학교, 2.친구, 3.팀플, 4.회의, 5.스터디, 6.취미, 7.봉사, 8.기타 - school = Category.builder() - .categoryName("학교") - .build(); - - friend = Category.builder() - .categoryName("친구") - .build(); - - teamProject = Category.builder() - .categoryName("팀플") - .build(); - - session = Category.builder() - .categoryName("회의") - .build(); - - study = Category.builder() - .categoryName("스터디") - .build(); - - hobby = Category.builder() - .categoryName("취미") - .build(); - - volunteer = Category.builder() - .categoryName("봉사") - .build(); - - etc = Category.builder() - .categoryName("기타") - .build(); - - categoryRepository.saveAll(List.of(school, friend, teamProject, session, study, hobby, volunteer, etc)); - } - - - @Test - @DisplayName("모임 생성 테스트 - 성공") - void testCreateMeeting_Success() throws Exception { - - // given - MeetingCreateRequestDto requestDto = MeetingDummy.createRequestDto( - List.of(teamProject.getCategoryId(), study.getCategoryId(), session.getCategoryId())); - - String json = objectMapper.writeValueAsString(requestDto); - - // expected - mockMvc.perform(post("/api/v1/meeting") - .contentType(MediaType.APPLICATION_JSON) - .content(json)) - .andExpectAll( - status().isCreated()) - .andDo(document("meeting/create/success", - preprocessRequest(prettyPrint()), - requestFields( - fieldWithPath("meetingName").description("모임 이름") - .attributes(key("constraint").value("모임 이름은 1자 이상 10자 이하로 입력해주세요.")), - fieldWithPath("meetingStartDate").description("모임 시작 날짜") - .attributes(key("constraint").value("모임 시작일은 종료일 이전이어야 합니다.")), - fieldWithPath("meetingEndDate").description("모임 종료 날짜") - .attributes(key("constraint").value("모임 종료일은 시작일 이후이어야 합니다.")), - fieldWithPath("numberOfPeople").description("모임 인원") - .attributes(key("constraint").value("모임 인원은 2명 이상 10명 이하로 설정해주세요.")), - fieldWithPath("isOnline").description("온라인 여부") - .optional() - .attributes(key("constraint").value("default = null")), - fieldWithPath("isAnonymous").description("익명 여부") - .attributes(key("constraint").value("default = false (실명)")), - fieldWithPath("voteEndDate").description("투표 종료 날짜") - .attributes(key("constraint").value("투표 종료일은 모임 시작일 이전이어야 합니다.")), - fieldWithPath("categoryIds").description("카테고리 아이디 목록") - .attributes(key("constraint").value("1개 이상의 카테고리를 선택해주세요.")) - ))); - } - - @Test - @DisplayName("모임 생성 테스트 - 실패") - void testCreateGroup_Fail() throws Exception { - - // given - MeetingCreateRequestDto requestDto = MeetingDummy.createInvalidRequestDto(); - String json = objectMapper.writeValueAsString(requestDto); - - // expected - mockMvc.perform(post("/api/v1/meeting") - .contentType(MediaType.APPLICATION_JSON) - .content(json)) - .andExpect(status().isBadRequest()) - .andDo(document("meeting/create/fail", - preprocessResponse(prettyPrint()), - responseFields( - fieldWithPath("code").description("상태 코드"), - fieldWithPath("message").description("에러 메시지"), - fieldWithPath("validation").description("유효성 검사 오류 목록"), - fieldWithPath("validation.meetingName").description("모임명은 필수 값입니다."), - fieldWithPath("validation.meetingStartDate").description("모임 일정 시작일은 필수 값입니다."), - fieldWithPath("validation.meetingEndDate").description("모임 일정 종료일은 필수 값입니다."), - fieldWithPath("validation.numberOfPeople").description("인원수는 필수 값입니다."), - fieldWithPath("validation.isAnonymous").description("익명 여부는 필수 값입니다."), - fieldWithPath("validation.voteEndDate").description("투표 종료일은 필수 값입니다."), - fieldWithPath("validation.categoryIds").description("카테고리는 최소 1개 이상 8개 이하로 선택해주세요.") - )) - ); - } - - @Test - @DisplayName("모임 조회 테스트 - 전체 조회") - void testGetMeetingList() throws Exception { - // given - List meetingList = MeetingDummy.createMeetingList(); - meetingRepository.saveAll(meetingList); - - // expected - mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/meeting") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(document("meeting/getList/success", - preprocessResponse(prettyPrint()), - responseFields( - fieldWithPath("[].meetingId").description("모임 아이디"), - fieldWithPath("[].meetingName").description("모임 이름"), - fieldWithPath("[].meetingStartDate").description("모임 시작 날짜"), - fieldWithPath("[].meetingEndDate").description("모임 종료 날짜"), - fieldWithPath("[].numberOfPeople").description("모임 인원"), - fieldWithPath("[].isOnline").description("온라인 여부"), - fieldWithPath("[].isAnonymous").description("익명 여부"), - fieldWithPath("[].voteEndDate").description("투표 종료 날짜") - ))); - } - - @Test - @DisplayName("모임 조회 테스트 - 단건 조회 (성공)") - void testGetMeeting_Success() throws Exception { - // given - Meeting meeting = Meeting.builder() - .meetingName("DND 7조 회의") - .meetingStartDate(LocalDate.of(2024, 7, 27)) - .meetingEndDate(LocalDate.of(2024, 7, 29)) - .numberOfPeople(6) - .isOnline(true) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) - .build(); - - meetingRepository.save(meeting); - - // expected - mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/meeting/{meetingId}", meeting.getMeetingId()) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpectAll( - jsonPath("$.meetingId").value(meeting.getMeetingId()), - jsonPath("$.meetingName").value(meeting.getMeetingName()), - jsonPath("$.meetingStartDate").value(meeting.getMeetingStartDate().toString()), - jsonPath("$.meetingEndDate").value(meeting.getMeetingEndDate().toString()), - jsonPath("$.numberOfPeople").value(meeting.getNumberOfPeople()), - jsonPath("$.isOnline").value(meeting.getIsOnline()), - jsonPath("$.isAnonymous").value(meeting.getIsAnonymous()), - jsonPath("$.voteEndDate").value(meeting.getVoteEndDate().toString()) - ) - .andDo(document("meeting/get/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("meetingId").description("모임 아이디")), - responseFields( - fieldWithPath("meetingId").description("모임 아이디"), - fieldWithPath("meetingName").description("모임 이름"), - fieldWithPath("meetingStartDate").description("모임 시작 날짜"), - fieldWithPath("meetingEndDate").description("모임 종료 날짜"), - fieldWithPath("numberOfPeople").description("모임 인원"), - fieldWithPath("isOnline").description("온라인 여부"), - fieldWithPath("isAnonymous").description("익명 여부"), - fieldWithPath("voteEndDate").description("투표 종료 날짜") - ))); - } - - @Test - @DisplayName("모임 조회 테스트 - 단건 조회 (실패)") - void testGetMeeting_Fail() throws Exception { - - // expected - mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/meeting/{meetingId}", Long.MAX_VALUE) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andDo(document("meeting/get/fail", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("meetingId").description("모임 아이디")), - responseFields( - fieldWithPath("code").description("상태 코드"), - fieldWithPath("message").description("에러 메시지"), - fieldWithPath("validation").description("유효성 검사 오류 목록") - ))); - } - - @Test - @DisplayName("모임 수정 테스트 - 성공") - void testUpdateMeeting_Success() throws Exception { - // given - Meeting meeting = Meeting.builder() - .meetingName("DND 7조 회의") - .meetingStartDate(LocalDate.of(2024, 7, 27)) - .meetingEndDate(LocalDate.of(2024, 7, 29)) - .numberOfPeople(6) - .isOnline(true) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) - .build(); - - Meeting saved = meetingRepository.save(meeting); - - MeetingUpdateRequestDto requestDto = MeetingDummy.updateRequestDto(session.getCategoryId()); - String json = objectMapper.writeValueAsString(requestDto); - - // expected - mockMvc.perform(RestDocumentationRequestBuilders.patch("/api/v1/meeting/{meetingId}", saved.getMeetingId()) - .contentType(MediaType.APPLICATION_JSON) - .content(json)) - .andExpect(status().isOk()) - .andDo(document("meeting/update/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("meetingId").description("모임 아이디")), - requestFields( - fieldWithPath("meetingName").description("모임 이름") - .attributes(key("constraint").value("모임 이름은 1자 이상 10자 이하로 입력해주세요.")), - fieldWithPath("meetingStartDate").description("모임 시작 날짜") - .attributes(key("constraint").value("모임 시작일은 종료일 이전이어야 합니다.")), - fieldWithPath("meetingEndDate").description("모임 종료 날짜") - .attributes(key("constraint").value("모임 종료일은 시작일 이후이어야 합니다.")), - fieldWithPath("numberOfPeople").description("모임 인원") - .attributes(key("constraint").value("모임 인원은 2명 이상 10명 이하로 설정해주세요.")), - fieldWithPath("isOnline").description("온라인 여부") - .optional() - .attributes(key("constraint").value("default = null")), - fieldWithPath("isAnonymous").description("익명 여부") - .attributes(key("constraint").value("default = false (실명)")), - fieldWithPath("voteEndDate").description("투표 종료 날짜") - .attributes(key("constraint").value("투표 종료일은 모임 시작일 이전이어야 합니다.")), - fieldWithPath("categoryIds").description("카테고리 아이디 목록") - .attributes(key("constraint").value("1개 이상의 카테고리를 선택해주세요.")) - ))); - } - - @Test - @DisplayName("모임 수정 테스트 - 실패 (존재하지 않는 모임)") - void testUpdateMeeting_Fail() throws Exception { - - // given - MeetingUpdateRequestDto requestDto = MeetingDummy.updateRequestDto(1L, 2L); - String json = objectMapper.writeValueAsString(requestDto); - - // expected - mockMvc.perform(RestDocumentationRequestBuilders.patch("/api/v1/meeting/{meetingId}", Long.MAX_VALUE) - .contentType(MediaType.APPLICATION_JSON) - .content(json)) - .andExpect(status().isNotFound()) - .andDo(print()) - .andDo(document("meeting/update/fail", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - responseFields( - fieldWithPath("code").description("상태 코드"), - fieldWithPath("message").description("에러 메시지"), - fieldWithPath("validation").description("유효성 검사 오류 목록") - ))); - } - - @Test - @DisplayName("모임 삭제 테스트 - 성공") - void testDeleteMeeting_Success() throws Exception { - // given - Meeting meeting = Meeting.builder() - .meetingName("DND 7조 회의") - .meetingStartDate(LocalDate.of(2024, 7, 27)) - .meetingEndDate(LocalDate.of(2024, 7, 29)) - .numberOfPeople(6) - .isOnline(true) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) - .build(); - - ReflectionTestUtils.setField(meeting, "meetingId", 1L); - meetingRepository.save(meeting); - - // expected - mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/v1/meeting/{meetingId}", 1L) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(document("meeting/delete/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("meetingId").description("모임 아이디")))); - } - - @Test - @DisplayName("모임 삭제 테스트 - 실패 (존재하지 않는 모임)") - void testDeleteMeeting_Fail() throws Exception { - - // expected - mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/v1/meeting/{meetingId}", Long.MAX_VALUE) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isNotFound()) - .andDo(document("meeting/delete/fail", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - pathParameters( - parameterWithName("meetingId").description("모임 아이디")), - responseFields( - fieldWithPath("code").description("상태 코드"), - fieldWithPath("message").description("에러 메시지"), - fieldWithPath("validation").description("유효성 검사 오류 목록")) - )); - } -} \ No newline at end of file From 34495a5bd3fa940b0a4b230eae92e7229de5fce1 Mon Sep 17 00:00:00 2001 From: seungjo Date: Fri, 2 Aug 2024 16:07:12 +0900 Subject: [PATCH 12/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20(meeting)=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/index.html | 728 ++---------------- .../meeting/service/MeetingServiceTest.java | 157 ++-- .../MeetingCategoryRepositoryTest.java | 2 + 3 files changed, 116 insertions(+), 771 deletions(-) diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 7407a21..ce96cb5 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -763,731 +763,129 @@

2. 모임

2.1. 모임 생성 - 성공

-
+
HTTP Request
-
-
POST /api/v1/meeting HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Content-Length: 256
-Host: 43.202.65.170.nip.io
-
-{
-  "categoryIds" : [ 47, 49, 48 ],
-  "meetingName" : "세븐일레븐",
-  "meetingStartDate" : "2024-07-27",
-  "meetingEndDate" : "2024-07-29",
-  "numberOfPeople" : 6,
-  "isOnline" : true,
-  "isAnonymous" : false,
-  "voteEndDate" : "2024-07-26T23:59:59"
-}
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/create/success/http-request.adoc[]

+
+
Request Fields
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/create/success/request-fields.adoc[]

- - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 4. Request Fields
PathTypeDescriptionOptionalConstraint

meetingName

String

모임 이름

모임 이름은 1자 이상 10자 이하로 입력해주세요.

meetingStartDate

String

모임 시작 날짜

모임 시작일은 종료일 이전이어야 합니다.

meetingEndDate

String

모임 종료 날짜

모임 종료일은 시작일 이후이어야 합니다.

numberOfPeople

Number

모임 인원

모임 인원은 2명 이상 10명 이하로 설정해주세요.

isOnline

Boolean

온라인 여부

true

default = null

isAnonymous

Boolean

익명 여부

default = false (실명)

voteEndDate

String

투표 종료 날짜

투표 종료일은 모임 시작일 이전이어야 합니다.

categoryIds

Array

카테고리 아이디 목록

1개 이상의 카테고리를 선택해주세요.

-
+
HTTP Response
-
-
HTTP/1.1 201 Created
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/create/success/http-response.adoc[]

2.2. 모임 생성 - 실패

-
+
HTTP Request
-
-
POST /api/v1/meeting HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Content-Length: 159
-Host: 43.202.65.170.nip.io
-
-{"categoryIds":[],"meetingName":null,"meetingStartDate":null,"meetingEndDate":null,"numberOfPeople":null,"isOnline":null,"isAnonymous":null,"voteEndDate":null}
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/create/fail/http-request.adoc[]

-
+
HTTP Response
-
-
HTTP/1.1 400 Bad Request
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-Content-Type: application/json
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-Content-Length: 572
-
-{
-  "code" : "400",
-  "message" : "잘못된 요청입니다.",
-  "validation" : {
-    "categoryIds" : "카테고리는 최소 1개 이상 8개 이하로 선택해주세요.",
-    "meetingEndDate" : "모임 일정 종료일은 필수 값입니다.",
-    "isAnonymous" : "익명 여부는 필수 값입니다.",
-    "meetingName" : "모임명은 필수 값입니다.",
-    "numberOfPeople" : "인원수는 필수 값입니다.",
-    "meetingStartDate" : "모임 일정 시작일은 필수 값입니다.",
-    "voteEndDate" : "투표 종료일은 필수 값입니다."
-  }
-}
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/create/fail/http-response.adoc[]

+
+
Error Response
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/create/fail/response-fields.adoc[]

- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 5. Error Response
PathTypeDescription

code

String

상태 코드

message

String

에러 메시지

validation

Object

유효성 검사 오류 목록

validation.meetingName

String

모임명은 필수 값입니다.

validation.meetingStartDate

String

모임 일정 시작일은 필수 값입니다.

validation.meetingEndDate

String

모임 일정 종료일은 필수 값입니다.

validation.numberOfPeople

String

인원수는 필수 값입니다.

validation.isAnonymous

String

익명 여부는 필수 값입니다.

validation.voteEndDate

String

투표 종료일은 필수 값입니다.

validation.categoryIds

String

카테고리는 최소 1개 이상 8개 이하로 선택해주세요.

2.3. 모임 전체 조회

- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 6. Response Fields
PathTypeDescription

[].meetingId

Number

모임 아이디

[].meetingName

String

모임 이름

[].meetingStartDate

String

모임 시작 날짜

[].meetingEndDate

String

모임 종료 날짜

[].numberOfPeople

Number

모임 인원

[].isOnline

Boolean

온라인 여부

[].isAnonymous

Boolean

익명 여부

[].voteEndDate

String

투표 종료 날짜

-
-
HTTP Request
-
-
GET /api/v1/meeting HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Host: 43.202.65.170.nip.io
+
+
Response Fields
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/getList/success/response-fields.adoc[]

+
+
HTTP Request
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/getList/success/http-request.adoc[]

-
+
HTTP Response
-
-
HTTP/1.1 200 OK
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-Content-Type: application/json
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-Content-Length: 730
-
-[ {
-  "meetingId" : 2,
-  "meetingName" : "세븐일레븐",
-  "meetingStartDate" : "2024-07-27",
-  "meetingEndDate" : "2024-07-29",
-  "numberOfPeople" : 6,
-  "isOnline" : true,
-  "isAnonymous" : false,
-  "voteEndDate" : "2024-07-26T23:59:59"
-}, {
-  "meetingId" : 3,
-  "meetingName" : "DND 7조 회의",
-  "meetingStartDate" : "2024-07-27",
-  "meetingEndDate" : "2024-07-29",
-  "numberOfPeople" : 6,
-  "isOnline" : true,
-  "isAnonymous" : false,
-  "voteEndDate" : "2024-07-26T23:59:59"
-}, {
-  "meetingId" : 4,
-  "meetingName" : "Java 스터디",
-  "meetingStartDate" : "2024-08-01",
-  "meetingEndDate" : "2024-08-05",
-  "numberOfPeople" : 4,
-  "isOnline" : true,
-  "isAnonymous" : false,
-  "voteEndDate" : "2024-07-30T23:59:59"
-} ]
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/getList/success/http-response.adoc[]

2.4. 모임 개별 조회 - 성공

- - ---- - - - - - - - - - - - - -
Table 7. /api/v1/meeting/{meetingId}
ParameterDescription

meetingId

모임 아이디

-
-
HTTP Request
-
-
GET /api/v1/meeting/1 HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Host: 43.202.65.170.nip.io
+
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/get/success/path-parameters.adoc[]

+
+
HTTP Request
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/get/success/http-request.adoc[]

-
+
HTTP Response
-
-
HTTP/1.1 200 OK
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-Content-Type: application/json
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-Content-Length: 241
-
-{
-  "meetingId" : 1,
-  "meetingName" : "DND 7조 회의",
-  "meetingStartDate" : "2024-07-27",
-  "meetingEndDate" : "2024-07-29",
-  "numberOfPeople" : 6,
-  "isOnline" : true,
-  "isAnonymous" : false,
-  "voteEndDate" : "2024-07-26T23:59:59"
-}
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/get/success/http-response.adoc[]

2.5. 모임 개별 조회 - 실패

- - ---- - - - - - - - - - - - - -
Table 8. /api/v1/meeting/{meetingId}
ParameterDescription

meetingId

모임 아이디

-
-
HTTP Request
-
-
GET /api/v1/meeting/9223372036854775807 HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Host: 43.202.65.170.nip.io
-
+
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/get/fail/path-parameters.adoc[]

-
-
-
HTTP/1.1 404 Not Found
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-Content-Type: application/json
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-Content-Length: 94
-
-{
-  "code" : "404",
-  "message" : "모임을 찾을 수 없습니다.",
-  "validation" : { }
-}
+
+
HTTP Request
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/get/fail/http-request.adoc[]

+
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/get/fail/http-response.adoc[]

2.6. 모임 수정 - 성공

- - ---- - - - - - - - - - - - - -
Table 9. /api/v1/meeting/{meetingId}
ParameterDescription

meetingId

모임 아이디

-
+
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/update/success/path-parameters.adoc[]

+
+
HTTP Request
-
-
PATCH /api/v1/meeting/5 HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Content-Length: 251
-Host: 43.202.65.170.nip.io
-
-{
-  "categoryIds" : [ 64 ],
-  "meetingName" : "DND 11기 모임",
-  "meetingStartDate" : "2024-08-11",
-  "meetingEndDate" : "2024-08-12",
-  "numberOfPeople" : 10,
-  "isOnline" : false,
-  "isAnonymous" : false,
-  "voteEndDate" : "2024-08-04T23:59:59"
-}
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/update/success/http-request.adoc[]

+
+
Request Fields
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/update/success/request-fields.adoc[]

- - ------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Table 10. Request Fields
PathTypeDescriptionOptionalConstraint

meetingName

String

모임 이름

모임 이름은 1자 이상 10자 이하로 입력해주세요.

meetingStartDate

String

모임 시작 날짜

모임 시작일은 종료일 이전이어야 합니다.

meetingEndDate

String

모임 종료 날짜

모임 종료일은 시작일 이후이어야 합니다.

numberOfPeople

Number

모임 인원

모임 인원은 2명 이상 10명 이하로 설정해주세요.

isOnline

Boolean

온라인 여부

true

default = null

isAnonymous

Boolean

익명 여부

default = false (실명)

voteEndDate

String

투표 종료 날짜

투표 종료일은 모임 시작일 이전이어야 합니다.

categoryIds

Array

카테고리 아이디 목록

1개 이상의 카테고리를 선택해주세요.

-
+
HTTP Response
-
-
HTTP/1.1 200 OK
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/update/success/http-response.adoc[]

2.7. 모임 수정 - 실패

-
+
HTTP Request
-
-
PATCH /api/v1/meeting/9223372036854775807 HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Content-Length: 253
-Host: 43.202.65.170.nip.io
-
-{
-  "categoryIds" : [ 1, 2 ],
-  "meetingName" : "DND 11기 모임",
-  "meetingStartDate" : "2024-08-11",
-  "meetingEndDate" : "2024-08-12",
-  "numberOfPeople" : 10,
-  "isOnline" : false,
-  "isAnonymous" : false,
-  "voteEndDate" : "2024-08-04T23:59:59"
-}
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/update/fail/http-request.adoc[]

-
-
+
HTTP Response
-
-
HTTP/1.1 404 Not Found
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-Content-Type: application/json
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-Content-Length: 94
-
-{
-  "code" : "404",
-  "message" : "모임을 찾을 수 없습니다.",
-  "validation" : { }
-}
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/update/fail/http-response.adoc[]

2.8. 모임 삭제 - 성공

- - ---- - - - - - - - - - - - - -
Table 11. /api/v1/meeting/{meetingId}
ParameterDescription

meetingId

모임 아이디

-
-
HTTP Request
-
-
DELETE /api/v1/meeting/1 HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Host: 43.202.65.170.nip.io
-
+
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/delete/success/path-parameters.adoc[] +.HTTP Request +Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/delete/success/http-request.adoc[]

-
+
HTTP Response
-
-
HTTP/1.1 200 OK
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/delete/success/http-response.adoc[]

2.9. 모임 삭제 - 실패

- - ---- - - - - - - - - - - - - -
Table 12. /api/v1/meeting/{meetingId}
ParameterDescription

meetingId

모임 아이디

-
-
HTTP Request
-
-
DELETE /api/v1/meeting/9223372036854775807 HTTP/1.1
-Content-Type: application/json;charset=UTF-8
-Host: 43.202.65.170.nip.io
+
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/delete/fail/path-parameters.adoc[]

+
+
HTTP Request
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/delete/fail/http-request.adoc[]

-
+
HTTP Response
-
-
HTTP/1.1 404 Not Found
-Vary: Origin
-Vary: Access-Control-Request-Method
-Vary: Access-Control-Request-Headers
-Content-Type: application/json
-X-Content-Type-Options: nosniff
-X-XSS-Protection: 0
-Cache-Control: no-cache, no-store, max-age=0, must-revalidate
-Pragma: no-cache
-Expires: 0
-X-Frame-Options: DENY
-Content-Length: 94
-
-{
-  "code" : "404",
-  "message" : "모임을 찾을 수 없습니다.",
-  "validation" : { }
-}
-
+

Unresolved directive in api/meeting.adoc - include::/Users/seungjo/development/dnd-11th-7-backend/build/generated-snippets/meeting/delete/fail/http-response.adoc[]

diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java index 9a64381..2481050 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java @@ -1,16 +1,17 @@ package com.dnd.jjakkak.domain.meeting.service; import com.dnd.jjakkak.domain.category.entity.Category; -import com.dnd.jjakkak.domain.category.exception.CategoryNotFoundException; import com.dnd.jjakkak.domain.category.repository.CategoryRepository; import com.dnd.jjakkak.domain.meeting.MeetingDummy; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; -import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.entity.Meeting; import com.dnd.jjakkak.domain.meeting.exception.MeetingNotFoundException; import com.dnd.jjakkak.domain.meeting.repository.MeetingRepository; import com.dnd.jjakkak.domain.meetingcategory.repository.MeetingCategoryRepository; +import com.dnd.jjakkak.domain.member.entity.Member; +import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -48,6 +49,12 @@ class MeetingServiceTest { @Mock MeetingCategoryRepository meetingCategoryRepository; + @Mock + JwtProvider jwtProvider; + + @Mock + MemberRepository memberRepository; + @Test @DisplayName("모임 생성 테스트 - 성공") void testCreateMeeting() { @@ -62,11 +69,18 @@ void testCreateMeeting() { .categoryName("회의") .build(); + Member member = Member.builder() + .kakaoId(1L) + .memberNickname("seungjo") + .build(); + when(categoryRepository.findById(1L)).thenReturn(Optional.of(teamProject)); when(categoryRepository.findById(2L)).thenReturn(Optional.of(meeting)); + when(jwtProvider.validate(anyString())).thenReturn("1"); + when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); // when - meetingService.createMeeting(actual); + meetingService.createMeeting("access_token", actual); // then verify(meetingRepository, times(1)).save(any()); @@ -75,53 +89,6 @@ void testCreateMeeting() { } - @Test - @DisplayName("모임 생성 테스트 - 실패 (카테고리 없음)") - void testCreateMeeting_Fail() { - - // given - MeetingCreateRequestDto actual = MeetingDummy.createRequestDto(List.of(1L, 2L)); - Category teamProject = Category.builder() - .categoryName("팀플") - .build(); - - when(categoryRepository.findById(1L)).thenReturn(Optional.of(teamProject)); - when(categoryRepository.findById(2L)).thenReturn(Optional.empty()); - - // expected - assertThrows(CategoryNotFoundException.class, - () -> meetingService.createMeeting(actual)); - } - - @Test - @DisplayName("모임 조회 테스트 - 전체") - void testGetMeetingList() { - - // given - List meetingList = MeetingDummy.createMeetingList(); - - when(meetingRepository.findAll()).thenReturn(meetingList); - - // when - List actual = meetingService.getMeetingList(); - - // then - assertAll( - () -> assertEquals(actual.size(), meetingList.size()), - () -> assertEquals(actual.get(0).getMeetingId(), meetingList.get(0).getMeetingId()), - () -> assertEquals(actual.get(0).getMeetingName(), meetingList.get(0).getMeetingName()), - () -> assertEquals(actual.get(0).getMeetingStartDate(), meetingList.get(0).getMeetingStartDate()), - () -> assertEquals(actual.get(0).getMeetingEndDate(), meetingList.get(0).getMeetingEndDate()), - () -> assertEquals(actual.get(0).getNumberOfPeople(), meetingList.get(0).getNumberOfPeople()), - () -> assertEquals(actual.get(0).getIsOnline(), meetingList.get(0).getIsOnline()), - () -> assertEquals(actual.get(0).getIsAnonymous(), meetingList.get(0).getIsAnonymous()), - () -> assertEquals(actual.get(0).getVoteEndDate(), meetingList.get(0).getVoteEndDate()) - ); - - verify(meetingRepository, times(1)).findAll(); - } - - @Test @DisplayName("모임 조회 테스트 - 단건 (성공)") void testGetMeeting_Success() { @@ -135,12 +102,14 @@ void testGetMeeting_Success() { .isOnline(true) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) + .meetingLeaderId(1L) + .meetingUuid("1234abcd") .build(); - when(meetingRepository.findById(anyLong())).thenReturn(Optional.of(meeting)); + when(meetingRepository.findByMeetingUuid(anyString())).thenReturn(Optional.of(meeting)); // when - MeetingResponseDto actual = meetingService.getMeeting(1L); + MeetingResponseDto actual = meetingService.getMeetingByUuid("1234abcd"); // then assertAll( @@ -151,95 +120,71 @@ void testGetMeeting_Success() { () -> assertEquals(actual.getNumberOfPeople(), meeting.getNumberOfPeople()), () -> assertEquals(actual.getIsOnline(), meeting.getIsOnline()), () -> assertEquals(actual.getIsAnonymous(), meeting.getIsAnonymous()), - () -> assertEquals(actual.getVoteEndDate(), meeting.getVoteEndDate()) + () -> assertEquals(actual.getVoteEndDate(), meeting.getVoteEndDate()), + () -> assertEquals(actual.getMeetingLeaderId(), meeting.getMeetingLeaderId()), + () -> assertEquals(actual.getMeetingUuid(), meeting.getMeetingUuid()) ); - verify(meetingRepository, times(1)).findById(1L); + verify(meetingRepository, times(1)).findByMeetingUuid(anyString()); } @Test @DisplayName("모임 조회 테스트 - 단건 (실패)") void testGetMeeting_Fail() { // given - when(meetingRepository.findById(anyLong())).thenReturn(Optional.empty()); + when(meetingRepository.findByMeetingUuid(anyString())).thenReturn(Optional.empty()); // expected assertThrows(MeetingNotFoundException.class, - () -> meetingService.getMeeting(1L)); + () -> meetingService.getMeetingByUuid("1234abcd")); - verify(meetingRepository, times(1)).findById(1L); + verify(meetingRepository, times(1)).findByMeetingUuid(anyString()); } - @Test - @DisplayName("모임 수정 테스트 - 성공") - void testUpdateMeeting_Success() { + @Test + @DisplayName("모임 삭제 테스트 - 성공") + void testDeleteMeeting_Success() { // given + Meeting meeting = Meeting.builder() - .meetingName("DND 7조 회의") - .meetingStartDate(LocalDate.of(2024, 7, 27)) - .meetingEndDate(LocalDate.of(2024, 7, 29)) - .numberOfPeople(6) - .isOnline(true) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) + .meetingLeaderId(1L) .build(); - ReflectionTestUtils.setField(meeting, "meetingId", 1L); - - when(meetingRepository.findById(1L)).thenReturn(Optional.of(meeting)); - - Category hobby = Category.builder() - .categoryName("취미") + Member member = Member.builder() + .kakaoId(1L) + .memberNickname("seungjo") .build(); - when(categoryRepository.findById(1L)).thenReturn(Optional.of(hobby)); - - // when - meetingService.updateMeeting(1L, MeetingDummy.updateRequestDto(1L)); - - // then - verify(meetingRepository, times(1)).findById(1L); - verify(categoryRepository, times(1)).findById(1L); - verify(meetingCategoryRepository, times(1)).deleteByMeetingId(1L); - verify(meetingCategoryRepository, times(1)).save(any()); - } + ReflectionTestUtils.setField(member, "memberId", 1L); - @Test - @DisplayName("모임 수정 테스트 - 실패 (모임 없음)") - void testUpdateMeeting_Fail() { - - // given - MeetingUpdateRequestDto meetingUpdateRequestDto = MeetingDummy.updateRequestDto(1L); - when(meetingRepository.findById(anyLong())).thenReturn(Optional.empty()); - - // expected - assertThrows(MeetingNotFoundException.class, - () -> meetingService.updateMeeting(1L, meetingUpdateRequestDto)); - } - - @Test - @DisplayName("모임 삭제 테스트 - 성공") - void testDeleteMeeting_Success() { - // given - when(meetingRepository.existsById(anyLong())).thenReturn(true); + when(jwtProvider.validate(anyString())).thenReturn("1"); + when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); + when(meetingRepository.findById(anyLong())).thenReturn(Optional.of(meeting)); // when - meetingService.deleteMeeting(1L); + meetingService.deleteMeeting("access_token", 1L); // then - verify(meetingRepository, times(1)).existsById(1L); verify(meetingRepository, times(1)).deleteById(1L); + verify(meetingCategoryRepository, times(1)).deleteByMeetingId(1L); } @Test @DisplayName("모임 삭제 테스트 - 실패 (존재하지 않는 모임)") void testDeleteMeeting_Fail() { // given - when(meetingRepository.existsById(anyLong())).thenReturn(false); + Member member = Member.builder() + .kakaoId(1L) + .memberNickname("seungjo") + .build(); + + + when(jwtProvider.validate(anyString())).thenReturn("1"); + when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); // expected assertThrows(MeetingNotFoundException.class, - () -> meetingService.deleteMeeting(1L)); + () -> meetingService.deleteMeeting("invalid_token", 1L)); } } \ No newline at end of file diff --git a/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java b/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java index 92db3cd..9cb563f 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java @@ -48,6 +48,8 @@ void testDeleteByMeetingId() { .isOnline(true) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) + .meetingLeaderId(1L) + .meetingUuid("12345678") .build(); entityManager.persist(meeting); From 8325b50e39439fb89712d20fc0f0e38f1d6d44cb Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Fri, 2 Aug 2024 17:28:51 +0900 Subject: [PATCH 13/49] =?UTF-8?q?feat:=20=EB=82=98=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80,=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/AuthController.java | 89 ++++-------- .../jwt/filter/JwtAuthenticationFilter.java | 129 ++++++++---------- .../jwt/handler/OAuth2FailureHandler.java | 37 +++++ .../jwt/handler/OAuth2LogoutHandler.java | 95 +++++++++++++ .../jwt/handler/OAuth2SuccessHandler.java | 4 +- .../member/jwt/provider/JwtProvider.java | 32 ++--- .../config/security/SecurityConfig.java | 22 ++- 7 files changed, 242 insertions(+), 166 deletions(-) create mode 100644 src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java create mode 100644 src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java diff --git a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java index e0e59ad..7794b6c 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java @@ -1,87 +1,52 @@ package com.dnd.jjakkak.domain.member.controller; -import com.dnd.jjakkak.domain.member.service.BlacklistService; -import com.dnd.jjakkak.domain.member.service.RefreshTokenService; -import com.dnd.jjakkak.global.exception.ErrorResponse; +import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.time.LocalDateTime; +import java.util.Collections; /** - * 로그아웃 시 사용하는 컨트롤러입니다. + * 현재 Member의 로그인 여부를 확인하는 컨트롤러입니다. * * @author 류태웅 - * @version 2024. 07. 27. + * @version 2024. 08. 02. */ -@Slf4j @RestController +@RequestMapping("/api/v1") @RequiredArgsConstructor public class AuthController { - private final RefreshTokenService refreshTokenService; - private final BlacklistService blacklistService; + + private final JwtProvider jwtProvider; /** - * 로그아웃을 할 때 사용합니다. - * 헤더에 Authorization : Bearer {refresh_token}을 입력한 다음 호출 - * 그렇게 되면 DB에 refresh_token이 삭제되고 블랙리스트에 추가됨 + * 로그아웃 시 프론트 측에서 보내는 access_token과 일치한 지 확인 후 + * 프론트엔드에게 로그인 또는 비로그인 상태의 메세지롤 보냄 * - * @param refreshToken String - * @return ResponseEntity + * @param request HttpServletRequest + * @return message ResponseEntity */ - @PostMapping("/api/v1/logout") - public ResponseEntity logout(@RequestHeader(value = "Authorization", required = false) String refreshToken) { - - // TODO: LoginHandler로 수정해보기 - - log.info("logout 시작"); - - if (refreshToken != null && refreshToken.startsWith("Bearer ")) { - refreshToken = refreshToken.substring(7); - log.info("logout refreshToken: {}", refreshToken); - - if (refreshTokenService.validateRefreshToken(refreshToken)) { - try { - refreshTokenService.deleteRefreshToken(refreshToken); - LocalDateTime expirationDate = LocalDateTime.now().plusWeeks(1); // 토큰의 실제 만료 시간으로 설정 - blacklistService.blacklistToken(refreshToken, expirationDate); - log.info("logout 성공"); - return ResponseEntity.ok().build(); - } catch (Exception e) { - log.error("서버 에러", e); - - ErrorResponse response = ErrorResponse.builder() - .code("500") - .message("Server Error") - .build(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + @GetMapping("/check-auth") + public ResponseEntity checkAuth(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("access_token")) { + String token = cookie.getValue(); + if (!jwtProvider.validate(token).isEmpty()) { + return ResponseEntity.ok().body(Collections.singletonMap("isAuthenticated", true)); + } } - } else { - log.error("Refresh Token 인증 오류"); - - ErrorResponse response = ErrorResponse.builder() - .code("401") - .message("Invalid Refresh Token") - .build(); - - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); } } - - log.error("헤더 인증 오류"); - - ErrorResponse response = ErrorResponse.builder() - .code("400") - .message("Invalid Header Error") - .build(); - - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + return ResponseEntity.ok().body(Collections.singletonMap("isAuthenticated", false)); } } + diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java index 3c82f73..f70bbda 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java @@ -7,6 +7,7 @@ import com.dnd.jjakkak.domain.member.repository.MemberRepository; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; import io.jsonwebtoken.MalformedJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -33,7 +34,7 @@ * 권한과 인증 방식을 검사하는 필터입니다. * * @author 류태웅 - * @version 2024. 07. 27. + * @version 2024. 08. 02. */ @Slf4j @@ -44,116 +45,98 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final MemberRepository memberRepository; private final RefreshTokenService refreshTokenService; - /** - * 검증을 수행하는 메소드입니다. - * 여기서 jwtProvider의 validate를 수행합니다. - * - * @param request HttpServletRequest - * @param response HttpServletResponse - * @param filterChain FilterChain - * @throws ServletException ServletException - * @throws IOException IOException - */ - @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - log.info("도착한 요청: {}", request.getPathInfo()); + log.info("도착한 요청: {}", request.getRequestURI()); String token = parseBearerToken(request); log.info("도착한 토큰: {}", token); - if(token == null){ // Bearer 인증 방식이 아니거나 빈 값일 경우 진행하지 말고 다음 필터로 바로 넘김 + + if (token == null) { filterChain.doFilter(request, response); return; } - String kakaoId; + + String kakaoId = null; try { kakaoId = jwtProvider.validate(token); log.info("검증된 카카오 ID: {}", kakaoId); } catch (ExpiredJwtException e) { log.error("엑세스 토큰이 만료됨", e); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"Token expired\"}"); - response.getWriter().flush(); + setErrorResponse(response, "Token expired"); return; - } catch (MalformedJwtException e){ + } catch (MalformedJwtException e) { log.error("손상된 토큰", e); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"Malformed Token\"}"); - response.getWriter().flush(); + setErrorResponse(response, "Malformed Token"); + return; + } catch (JwtException e) { + log.error("토큰 검증 오류", e); + setErrorResponse(response, "Invalid Token"); return; } - if (kakaoId == null) { // AccessToken 검증 실패 시 - String refreshToken = parseRefreshToken(request); - log.info("도착한 리프레시 토큰: {}", refreshToken); - if (refreshToken != null && refreshTokenService.validateRefreshToken(refreshToken)) { - // RefreshToken이 유효한 경우 새로운 AccessToken 발급 - kakaoId = jwtProvider.getSubjectFromRefreshToken(refreshToken); - token = jwtProvider.createAccessToken(kakaoId); - response.setHeader("Authorization", "Bearer " + token); - log.info("새로 발급된 엑세스 토큰: {}", token); - } else { - // RefreshToken도 유효하지 않으면 로그아웃 처리 및 프론트엔드로 메시지 전송 - log.error("엑세스 토큰이 만료되었고 해당되는 리프레시 토큰이 존재하지 않습니다."); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"Invalid Refresh Token\"}"); - response.getWriter().flush(); - return; - } + + if (kakaoId == null) { + handleTokenException(response, request); + return; } + + processAuthentication(kakaoId, request); + filterChain.doFilter(request, response); + } + + private void processAuthentication(String kakaoId, HttpServletRequest request) { Member member = memberRepository.findByKakaoId(Long.parseLong(kakaoId)) .orElseThrow(MemberNotFoundException::new); - Role role = member.getRole(); // ROLE_USER, ROLE_ADMIN + Role role = member.getRole(); - // 예시 : ROLE_MASTER, ROLE_DEVELOPER List authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority(role.toString())); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(kakaoId, null, authorities); // pwd는 토큰에 추가X -> null + AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(kakaoId, null, authorities); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); securityContext.setAuthentication(authenticationToken); SecurityContextHolder.setContext(securityContext); + } - filterChain.doFilter(request, response); + private void handleTokenException(HttpServletResponse response, HttpServletRequest request) throws IOException { + String refreshToken = parseRefreshToken(request); + log.info("도착한 리프레시 토큰: {}", refreshToken); + + if (refreshToken != null && refreshTokenService.validateRefreshToken(refreshToken)) { + String kakaoId = jwtProvider.getSubjectFromRefreshToken(refreshToken); + String newToken = jwtProvider.createAccessToken(kakaoId); + response.setHeader("Authorization", "Bearer " + newToken); + log.info("새로 발급된 엑세스 토큰: {}", newToken); + processAuthentication(kakaoId, request); + } else { + log.error("엑세스 토큰이 만료되었고 해당되는 리프레시 토큰이 존재하지 않습니다."); + setErrorResponse(response, "Invalid Refresh Token"); + } } - /** - * 헤더를 검증하는 메소드입니다. - * @param request HttpServletRequest - * @return authorization String - */ + private void setErrorResponse(HttpServletResponse response, String message) throws IOException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"" + message + "\"}"); + response.getWriter().flush(); + } private String parseBearerToken(HttpServletRequest request) { - String authorization = request.getHeader("Authorization"); // 응답의 헤더에서 뽑아옴 + String authorization = request.getHeader("Authorization"); - boolean hasAuthorization = StringUtils.hasText(authorization); - if(!hasAuthorization) return null; // null이거나 문자가 아닌 경우 null - - boolean isBearer = authorization.startsWith("Bearer "); - if(!isBearer) return null; // 아닐 경우 Bearer 인증 방신이 아님 -> null - - String token = authorization.substring(7); - if ("undefined".equalsIgnoreCase(token)) { - return null; + if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) { + String token = authorization.substring(7); + if (!"undefined".equalsIgnoreCase(token)) { + return token; + } } - return token; + return null; } - /** - * 헤더에서 RefreshToken을 검증하는 메소드입니다. - * @param request HttpServletRequest - * @return refreshToken String - */ private String parseRefreshToken(HttpServletRequest request) { String refreshToken = request.getHeader("RefreshToken"); - - boolean hasRefreshToken = StringUtils.hasText(refreshToken); - if (!hasRefreshToken) return null; - - return refreshToken; + return StringUtils.hasText(refreshToken) ? refreshToken : null; } -} \ No newline at end of file +} diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java new file mode 100644 index 0000000..00acae1 --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java @@ -0,0 +1,37 @@ +package com.dnd.jjakkak.domain.member.jwt.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URLEncoder; + +/** + * 검증 실패 시 예외 처리 링크로 이동하는 핸들러입니다. + * + * @author 류태웅 + * @version 2024. 08. 02. + */ + +@Component +public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { + String errorMessage; + if (e instanceof UsernameNotFoundException){ + errorMessage="존재하지 않는 아이디 입니다."; + } + else{ + errorMessage="알 수 없는 이유로 로그인이 안되고 있습니다."; + } + + errorMessage= URLEncoder.encode(errorMessage,"UTF-8");//한글 인코딩 깨지는 문제 방지 + response.sendRedirect("http://localhost:8080/auth/oauth-response/error="+errorMessage); + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java new file mode 100644 index 0000000..b77f33b --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java @@ -0,0 +1,95 @@ +package com.dnd.jjakkak.domain.member.jwt.handler; + +import com.dnd.jjakkak.domain.member.service.BlacklistService; +import com.dnd.jjakkak.domain.member.service.RefreshTokenService; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * 로그아웃 시 토큰에 빈 값을 넣어 전송 하는 방식으로 토큰을 삭제하는 핸들러입니다. + * + * @author 류태웅 + * @version 2024. 08. 02. + */ + +@Slf4j +@Component +@RequiredArgsConstructor +public class OAuth2LogoutHandler implements LogoutHandler { + private final RefreshTokenService refreshTokenService; + private final BlacklistService blacklistService; + + /** + * 로그아웃 시 프론트 측에서 보내는 refresh_token과 일치한 지 확인 후 + * 프론트엔드에게 빈 값의 쿠키를 보냄으로써 쿠키 삭제 (프론트에서도 쿠키 삭제 필요) + * + * @param request HttpServletRequest + * @param response HttpServletResponse + * @param authentication Authentication + */ + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + String refreshToken = null; + + Cookie[] cookies = request.getCookies(); + if (cookies != null) { + for (Cookie cookie : cookies) { + if ("refresh_token".equals(cookie.getName())) { + refreshToken = cookie.getValue(); + break; + } + } + } + + log.info("logout 시작"); + + if (refreshToken != null) { + log.info("logout refreshToken: {}", refreshToken); + + if (refreshTokenService.validateRefreshToken(refreshToken)) { + try { + refreshTokenService.deleteRefreshToken(refreshToken); + LocalDateTime expirationDate = LocalDateTime.now().plusWeeks(1); + blacklistService.blacklistToken(refreshToken, expirationDate); + log.info("logout 성공"); + + // 쿠키 삭제 + Cookie accessTokenCookie = new Cookie("access_token", null); + accessTokenCookie.setPath("/"); + accessTokenCookie.setHttpOnly(true); + accessTokenCookie.setSecure(true); + accessTokenCookie.setMaxAge(0); + + Cookie refreshTokenCookie = new Cookie("refresh_token", null); + refreshTokenCookie.setPath("/"); + refreshTokenCookie.setHttpOnly(true); + refreshTokenCookie.setSecure(true); + refreshTokenCookie.setMaxAge(0); + + response.addCookie(accessTokenCookie); + response.addCookie(refreshTokenCookie); + + response.setStatus(HttpServletResponse.SC_OK); + } catch (Exception e) { + log.error("서버 에러", e); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } else { + log.error("Refresh Token 인증 오류"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + } else { + log.error("헤더 인증 오류"); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + } + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java index 4b2d300..f5ac297 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java @@ -19,7 +19,7 @@ * 검증 성공 시 토큰이 저장된 링크로 이동하는 핸들러입니다. * * @author 류태웅 - * @version 2024. 07. 27. + * @version 2024. 08. 02. */ @Slf4j @@ -68,6 +68,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.addCookie(refreshTokenCookie); // 로그인 성공 시 리다이렉트되는 URL은 추후 수정 필요 - response.sendRedirect("http://localhost:8080/auth/oauth-response/"); + response.sendRedirect("http://localhost:3000/"); } } diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java index ef277a4..42f594a 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java @@ -16,7 +16,7 @@ * JWT를 만들고 검증하는 Provider입니다. * * @author 류태웅 - * @version 2024. 07. 27. + * @version 2024. 08. 02. */ @Slf4j @@ -75,30 +75,14 @@ public String createRefreshToken(String kakaoId) { * @return subject */ - public String validate(String jwt) { - String subject; - try { - Claims claims = Jwts.parserBuilder() - .setSigningKey(key) - .build() - .parseClaimsJws(jwt) - .getBody(); - subject = claims.getSubject(); - log.info("subject, {}", subject); - } catch (ExpiredJwtException e) { - log.error("JWT 만료", e); - return null; - } catch (MalformedJwtException e) { - log.error("잘못된 토큰", e); - return null; - } - catch (Exception e) { - log.error("JWT 검증 실패", e); - return null; - } - return subject; + public String validate(String jwt) throws ExpiredJwtException, MalformedJwtException, JwtException { + Claims claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(jwt) + .getBody(); + return claims.getSubject(); } - /** * RefreshToken에서 subject를 추출하는 메소드 * diff --git a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java index 309a541..d59cd05 100644 --- a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java +++ b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java @@ -1,7 +1,10 @@ package com.dnd.jjakkak.global.config.security; import com.dnd.jjakkak.domain.member.jwt.filter.JwtAuthenticationFilter; +import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2FailureHandler; +import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2LogoutHandler; import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2SuccessHandler; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.Bean; @@ -18,11 +21,13 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.util.Arrays; + /** * Spring Security Configuration Class. * * @author 류태웅 - * @version 2024. 07. 27. + * @version 2024. 08. 02. */ @Configurable @@ -34,6 +39,8 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final DefaultOAuth2UserService oAuth2UserService; private final OAuth2SuccessHandler oAuth2SuccessHandler; + private final OAuth2FailureHandler oAuth2FailureHandler; + private final OAuth2LogoutHandler oAuth2LogoutHandler; /** * Security Bean 등록. @@ -69,6 +76,12 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .redirectionEndpoint(endpoint -> endpoint.baseUri("/oauth2/callback/*")) .userInfoEndpoint(endpoint -> endpoint.userService(oAuth2UserService)) .successHandler(oAuth2SuccessHandler) + .failureHandler(oAuth2FailureHandler) + ) + .logout(logout -> logout + .addLogoutHandler(oAuth2LogoutHandler) + .logoutUrl("/api/v1/logout") + .logoutSuccessHandler((request, response, authentication) -> response.setStatus(HttpServletResponse.SC_OK)) ) .exceptionHandling(exceptionHandling -> exceptionHandling // 실패 시 해당 메시지 반환 .authenticationEntryPoint(new FailedAuthenticationEntryPoint()) @@ -81,7 +94,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { /** * CORS 설정 용 메소드 추가 * - *
  • 추후 수정 필요
  • + *
  • credentials를 허용하기 위해 특정 도메인 추가
  • * * @return CorsConfigurationSource */ @@ -89,9 +102,10 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Bean protected CorsConfigurationSource corsConfigurationSource() { // 추후 CORS 수정 필요 CorsConfiguration config = new CorsConfiguration(); - config.addAllowedOrigin("*"); + config.setAllowedOrigins(Arrays.asList("http://localhost:3000")); // 허용할 도메인 명시 config.addAllowedMethod("*"); config.addAllowedHeader("*"); + config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); @@ -109,6 +123,4 @@ WebSecurityCustomizer webSecurityCustomizer() { return web -> web.ignoring() .requestMatchers("/favicon"); } - - } From d433bfbcd4db78f7bd6af135d22280a5e9936e39 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Fri, 2 Aug 2024 17:29:39 +0900 Subject: [PATCH 14/49] =?UTF-8?q?refactor:=20UTF-8=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/jwt/handler/OAuth2FailureHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java index 00acae1..0290b53 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; /** * 검증 실패 시 예외 처리 링크로 이동하는 핸들러입니다. @@ -31,7 +32,7 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo errorMessage="알 수 없는 이유로 로그인이 안되고 있습니다."; } - errorMessage= URLEncoder.encode(errorMessage,"UTF-8");//한글 인코딩 깨지는 문제 방지 + errorMessage= URLEncoder.encode(errorMessage, StandardCharsets.UTF_8);//한글 인코딩 깨지는 문제 방지 response.sendRedirect("http://localhost:8080/auth/oauth-response/error="+errorMessage); } } From d33a0b88114ef4d8039e21eb1486295782cc96d4 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Fri, 2 Aug 2024 19:26:17 +0900 Subject: [PATCH 15/49] =?UTF-8?q?refactor:=20access=20Token=EC=9D=84=20hea?= =?UTF-8?q?der=EB=A1=9C=20=EB=B3=B4=EB=82=B4=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/AuthController.java | 29 ++++---- .../jwt/filter/JwtAuthenticationFilter.java | 69 ++++--------------- .../jwt/handler/OAuth2LogoutHandler.java | 20 ------ .../jwt/handler/OAuth2SuccessHandler.java | 35 +++------- .../config/security/SecurityConfig.java | 3 +- 5 files changed, 42 insertions(+), 114 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java index 7794b6c..ecddf46 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java @@ -1,9 +1,9 @@ package com.dnd.jjakkak.domain.member.controller; import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -16,8 +16,9 @@ * * @author 류태웅 * @version 2024. 08. 02. + * */ - +@Slf4j @RestController @RequestMapping("/api/v1") @RequiredArgsConstructor @@ -26,8 +27,8 @@ public class AuthController { private final JwtProvider jwtProvider; /** - * 로그아웃 시 프론트 측에서 보내는 access_token과 일치한 지 확인 후 - * 프론트엔드에게 로그인 또는 비로그인 상태의 메세지롤 보냄 + * 프론트 측에서 보내는 access_token을 확인 후 + * 프론트엔드에게 로그인 또는 비로그인 상태의 메시지를 보냄 * * @param request HttpServletRequest * @return message ResponseEntity @@ -35,18 +36,18 @@ public class AuthController { @GetMapping("/check-auth") public ResponseEntity checkAuth(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookie.getName().equals("access_token")) { - String token = cookie.getValue(); - if (!jwtProvider.validate(token).isEmpty()) { - return ResponseEntity.ok().body(Collections.singletonMap("isAuthenticated", true)); - } - } + String authorizationHeader = request.getHeader("Authorization"); + log.info(authorizationHeader); + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + String token = authorizationHeader.substring(7); + String subject = jwtProvider.validate(token); + + if (subject != null && !subject.isEmpty()) { + log.info("isAuthenticated"); + return ResponseEntity.ok().body(Collections.singletonMap("isAuthenticated", true)); } } + log.info("isNotAuthenticated"); return ResponseEntity.ok().body(Collections.singletonMap("isAuthenticated", false)); } } - diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java index f70bbda..f5e6aa4 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java @@ -5,9 +5,7 @@ import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; -import com.dnd.jjakkak.domain.member.service.RefreshTokenService; import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtException; import io.jsonwebtoken.MalformedJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -31,8 +29,7 @@ import java.util.List; /** - * 권한과 인증 방식을 검사하는 필터입니다. - * + * 검증을 실행하는 필터입니다. * @author 류태웅 * @version 2024. 08. 02. */ @@ -43,47 +40,40 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtProvider jwtProvider; private final MemberRepository memberRepository; - private final RefreshTokenService refreshTokenService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - log.info("도착한 요청: {}", request.getRequestURI()); String token = parseBearerToken(request); log.info("도착한 토큰: {}", token); - - if (token == null) { + if (token == null) { // Bearer 인증 방식이 아니거나 빈 값일 경우 진행하지 말고 다음 필터로 바로 넘김 filterChain.doFilter(request, response); return; } - - String kakaoId = null; + String kakaoId; try { kakaoId = jwtProvider.validate(token); log.info("검증된 카카오 ID: {}", kakaoId); } catch (ExpiredJwtException e) { log.error("엑세스 토큰이 만료됨", e); - setErrorResponse(response, "Token expired"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Token expired\"}"); + response.getWriter().flush(); return; } catch (MalformedJwtException e) { log.error("손상된 토큰", e); - setErrorResponse(response, "Malformed Token"); - return; - } catch (JwtException e) { - log.error("토큰 검증 오류", e); - setErrorResponse(response, "Invalid Token"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write("{\"error\": \"Malformed Token\"}"); + response.getWriter().flush(); return; } if (kakaoId == null) { - handleTokenException(response, request); + filterChain.doFilter(request, response); return; } - processAuthentication(kakaoId, request); - filterChain.doFilter(request, response); - } - - private void processAuthentication(String kakaoId, HttpServletRequest request) { Member member = memberRepository.findByKakaoId(Long.parseLong(kakaoId)) .orElseThrow(MemberNotFoundException::new); Role role = member.getRole(); @@ -97,46 +87,15 @@ private void processAuthentication(String kakaoId, HttpServletRequest request) { securityContext.setAuthentication(authenticationToken); SecurityContextHolder.setContext(securityContext); - } - - private void handleTokenException(HttpServletResponse response, HttpServletRequest request) throws IOException { - String refreshToken = parseRefreshToken(request); - log.info("도착한 리프레시 토큰: {}", refreshToken); - if (refreshToken != null && refreshTokenService.validateRefreshToken(refreshToken)) { - String kakaoId = jwtProvider.getSubjectFromRefreshToken(refreshToken); - String newToken = jwtProvider.createAccessToken(kakaoId); - response.setHeader("Authorization", "Bearer " + newToken); - log.info("새로 발급된 엑세스 토큰: {}", newToken); - processAuthentication(kakaoId, request); - } else { - log.error("엑세스 토큰이 만료되었고 해당되는 리프레시 토큰이 존재하지 않습니다."); - setErrorResponse(response, "Invalid Refresh Token"); - } - } - - private void setErrorResponse(HttpServletResponse response, String message) throws IOException { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"" + message + "\"}"); - response.getWriter().flush(); + filterChain.doFilter(request, response); } private String parseBearerToken(HttpServletRequest request) { String authorization = request.getHeader("Authorization"); - if (StringUtils.hasText(authorization) && authorization.startsWith("Bearer ")) { - String token = authorization.substring(7); - if (!"undefined".equalsIgnoreCase(token)) { - return token; - } + return authorization.substring(7); } - return null; } - - private String parseRefreshToken(HttpServletRequest request) { - String refreshToken = request.getHeader("RefreshToken"); - return StringUtils.hasText(refreshToken) ? refreshToken : null; - } } diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java index b77f33b..a078e89 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java @@ -16,10 +16,7 @@ /** * 로그아웃 시 토큰에 빈 값을 넣어 전송 하는 방식으로 토큰을 삭제하는 핸들러입니다. * - * @author 류태웅 - * @version 2024. 08. 02. */ - @Slf4j @Component @RequiredArgsConstructor @@ -27,15 +24,6 @@ public class OAuth2LogoutHandler implements LogoutHandler { private final RefreshTokenService refreshTokenService; private final BlacklistService blacklistService; - /** - * 로그아웃 시 프론트 측에서 보내는 refresh_token과 일치한 지 확인 후 - * 프론트엔드에게 빈 값의 쿠키를 보냄으로써 쿠키 삭제 (프론트에서도 쿠키 삭제 필요) - * - * @param request HttpServletRequest - * @param response HttpServletResponse - * @param authentication Authentication - */ - @Override public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { String refreshToken = null; @@ -62,20 +50,12 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut blacklistService.blacklistToken(refreshToken, expirationDate); log.info("logout 성공"); - // 쿠키 삭제 - Cookie accessTokenCookie = new Cookie("access_token", null); - accessTokenCookie.setPath("/"); - accessTokenCookie.setHttpOnly(true); - accessTokenCookie.setSecure(true); - accessTokenCookie.setMaxAge(0); - Cookie refreshTokenCookie = new Cookie("refresh_token", null); refreshTokenCookie.setPath("/"); refreshTokenCookie.setHttpOnly(true); refreshTokenCookie.setSecure(true); refreshTokenCookie.setMaxAge(0); - response.addCookie(accessTokenCookie); response.addCookie(refreshTokenCookie); response.setStatus(HttpServletResponse.SC_OK); diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java index f5ac297..a2b4172 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java @@ -17,11 +17,9 @@ /** * 검증 성공 시 토큰이 저장된 링크로 이동하는 핸들러입니다. - * * @author 류태웅 * @version 2024. 08. 02. */ - @Slf4j @Component @RequiredArgsConstructor @@ -30,44 +28,33 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler private final JwtProvider jwtProvider; private final RefreshTokenService refreshTokenService; - /** - * 해당 메소드로 성공 시 링크로 이동합니다. - * 여기서 jwtProvider의 create를 수행합니다. - * - * @param request HttpServletRequest - * @param response HttpServletResponse - * @param authentication Authentication - * @throws IOException IOException - * @throws ServletException ServletException - */ - @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Member oauth2User = (Member) authentication.getPrincipal(); String kakaoId = Long.toString(oauth2User.getKakaoId()); // Access Token 생성 - String token = jwtProvider.createAccessToken(kakaoId); + String accessToken = jwtProvider.createAccessToken(kakaoId); // Refresh Token 생성 및 저장 String refreshToken = jwtProvider.createRefreshToken(kakaoId); refreshTokenService.createRefreshToken(oauth2User.getMemberId(), refreshToken); - Cookie accessTokenCookie = new Cookie("access_token", token); - accessTokenCookie.setSecure(true); - accessTokenCookie.setHttpOnly(true); - accessTokenCookie.setMaxAge(60*30); // 만료 시간 : 30분 - accessTokenCookie.setPath("/"); // 모든 경로에서 접근 가능하도록 설정 (초 단위) - response.addCookie(accessTokenCookie); + log.info("access token: " + accessToken); + log.info("refresh token: " + refreshToken); + // Refresh Token 쿠키 설정 Cookie refreshTokenCookie = new Cookie("refresh_token", refreshToken); refreshTokenCookie.setSecure(true); refreshTokenCookie.setHttpOnly(true); - refreshTokenCookie.setMaxAge(60*60*24*7); // 만료 시간 : 1wn - refreshTokenCookie.setPath("/"); // 모든 경로에서 접근 가능하도록 설정 (초 단위) - response.addCookie(refreshTokenCookie); + refreshTokenCookie.setPath("/"); // 모든 경로에서 접근 가능하도록 설정 + refreshTokenCookie.setMaxAge(60 * 60 * 24 * 7); // 1주일 - // 로그인 성공 시 리다이렉트되는 URL은 추후 수정 필요 + // 쿠키 추가 + response.addCookie(refreshTokenCookie); + // Access Token을 헤더에 추가 + response.setHeader("Authorization", "Bearer " + accessToken); + // 리다이렉트할 URL 설정 (프론트엔드 페이지로 리다이렉트) response.sendRedirect("http://localhost:3000/"); } } diff --git a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java index d59cd05..09d7b29 100644 --- a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java +++ b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java @@ -104,7 +104,8 @@ protected CorsConfigurationSource corsConfigurationSource() { // 추후 CORS 수 CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(Arrays.asList("http://localhost:3000")); // 허용할 도메인 명시 config.addAllowedMethod("*"); - config.addAllowedHeader("*"); + config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Access-Control-Allow-Headers", "Access-Control-Expose-Headers")); + config.addExposedHeader("Authorization"); //프론트에서 해당 헤더를 읽을 수 있게 config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); From b6329042c06a5b12271a554ba19a571c7df43ec0 Mon Sep 17 00:00:00 2001 From: seungjo Date: Sat, 3 Aug 2024 14:35:39 +0900 Subject: [PATCH 16/49] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20-=20Authorization=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/controller/MeetingController.java | 17 ++++++++++------- .../dto/request/MeetingCreateRequestDto.java | 2 +- .../domain/meeting/service/MeetingService.java | 5 +++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java index 5ad254f..291bdc9 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java @@ -6,7 +6,6 @@ import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; -import jakarta.servlet.http.Cookie; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -29,7 +28,6 @@ public class MeetingController { private final MeetingService meetingService; - /** * 모임을 생성하는 메서드입니다. * @@ -38,7 +36,7 @@ public class MeetingController { * @return 201 (CREATED), body: 모임 생성 응답 DTO (UUID) */ @PostMapping - public ResponseEntity createGroup(@CookieValue("access_token") Cookie accessToken, + public ResponseEntity createGroup(@RequestHeader("Authorization") String accessToken, @Valid @RequestBody MeetingCreateRequestDto requestDto) { if (Objects.isNull(accessToken)) { @@ -47,7 +45,7 @@ public ResponseEntity createGroup(@CookieValue("access return ResponseEntity .status(HttpStatus.CREATED) - .body(meetingService.createMeeting(accessToken.getValue(), requestDto)); + .body(meetingService.createMeeting(accessToken, requestDto)); } /** @@ -89,14 +87,19 @@ public ResponseEntity confirmMeeting(@PathVariable("meetingId") Long id, /** * 모임을 삭제하는 메서드입니다. * - * @param id 삭제할 모임 ID + * @param accessToken JWT Token (Access Token) + * @param id 삭제할 모임 ID * @return 200 (OK) */ @DeleteMapping("/{meetingId}") - public ResponseEntity deleteMeeting(@CookieValue("access_token") Cookie accessToken, + public ResponseEntity deleteMeeting(@RequestHeader("Authorization") String accessToken, @PathVariable("meetingId") Long id) { - meetingService.deleteMeeting(accessToken.getValue(), id); + if (Objects.isNull(accessToken)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + meetingService.deleteMeeting(accessToken, id); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java index f752d11..7b45d1f 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java @@ -24,7 +24,7 @@ @ToString public class MeetingCreateRequestDto { - @Size(min = 1, max = 8, message = "카테고리는 최소 1개 이상 8개 이하로 선택해주세요.") + @Size(min = 1, max = 3, message = "카테고리는 최소 1개 이상 3개 이하로 선택해주세요.") private final List categoryIds = new ArrayList<>(); @NotBlank(message = "모임명은 필수 값입니다.") diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index d88a0fa..a245efb 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -98,8 +98,8 @@ public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequest /** * 모임에 속한 회원 조회 * - * @param id MeetingId - * @return List + * @param id 조회할 모임 ID + * @return 회원 응답 DTO */ @Transactional(readOnly = true) public List getMeetingListByMeetingId(Long id) { @@ -187,6 +187,7 @@ private String generateUuid() { */ private Member getMemberByToken(String token) { String kakaoId = Objects.requireNonNull(jwtProvider.validate(token)); + return memberRepository.findByKakaoId(Long.parseLong(kakaoId)) .orElseThrow(MemberNotFoundException::new); } From e37813de196a3c5e37637ea6b1ddf7b2330401b1 Mon Sep 17 00:00:00 2001 From: seungjo Date: Sat, 3 Aug 2024 15:04:40 +0900 Subject: [PATCH 17/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/MeetingUpdateRequestDto.java | 82 ------------------- .../domain/meeting/entity/Meeting.java | 16 ---- .../jjakkak/domain/meeting/MeetingDummy.java | 15 ---- 3 files changed, 113 deletions(-) delete mode 100644 src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingUpdateRequestDto.java diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingUpdateRequestDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingUpdateRequestDto.java deleted file mode 100644 index 6377514..0000000 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingUpdateRequestDto.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.dnd.jjakkak.domain.meeting.dto.request; - -import com.dnd.jjakkak.domain.meeting.exception.InvalidMeetingDateException; -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.ToString; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; - -/** - * 모임 수정 요청 DTO 클래스입니다. - * - * @author 정승조 - * @version 2024. 07. 28. - */ -@Getter -@ToString -public class MeetingUpdateRequestDto { - - @Size(min = 1, max = 8, message = "카테고리는 최소 1개 이상 8개 이하로 선택해주세요.") - private final List categoryIds = new ArrayList<>(); - - @NotBlank(message = "모임명은 필수 값입니다.") - @Size(max = 30, message = "모임명을 30자 이내로 입력해주세요.") - private String meetingName; - - @NotNull(message = "모임 일정 시작일은 필수 값입니다.") - private LocalDate meetingStartDate; - - @NotNull(message = "모임 일정 종료일은 필수 값입니다.") - private LocalDate meetingEndDate; - - @NotNull(message = "인원수는 필수 값입니다.") - @Max(value = 10, message = "인원수는 10명 이하로 입력해주세요.") - private Integer numberOfPeople; - - private Boolean isOnline; - - @NotNull(message = "익명 여부는 필수 값입니다.") - private Boolean isAnonymous; - - @NotNull(message = "투표 종료일은 필수 값입니다.") - private LocalDateTime voteEndDate; - - /** - * 모임 일정을 검증하는 메서드입니다. - * - * @throws InvalidMeetingDateException 모임 일정이 유효하지 않을 경우 발생합니다. - */ - public void checkMeetingDate() { - InvalidMeetingDateException invalidMeetingDateException = new InvalidMeetingDateException(); - - if (meetingStartDate.isAfter(meetingEndDate)) { - invalidMeetingDateException.addValidation( - "meetingStartDate", - "모임 시작일은 종료일 이전으로 설정해주세요."); - } - - if (voteEndDate.isAfter(meetingStartDate.atStartOfDay())) { - invalidMeetingDateException.addValidation( - "voteEndDate", - "투표 종료일은 모임 시작일 이전으로 설정해주세요."); - } - - if (ChronoUnit.DAYS.between(meetingEndDate, meetingStartDate) >= 14) { - invalidMeetingDateException.addValidation( - "meetingStartDate, meetingEndDate", - "모임 시작일과 종료일은 최대 14일까지 설정 가능합니다."); - } - - if (!invalidMeetingDateException.getValidation().isEmpty()) { - throw invalidMeetingDateException; - } - } -} diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java index 6b9ebfd..6f8c743 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java @@ -1,7 +1,6 @@ package com.dnd.jjakkak.domain.meeting.entity; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; -import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; import com.dnd.jjakkak.domain.meeting.exception.InvalidMeetingDateException; import jakarta.persistence.*; import lombok.*; @@ -90,19 +89,4 @@ public void updateConfirmedSchedule(MeetingConfirmRequestDto requestDto) { this.confirmedSchedule = requestDto.getConfirmedSchedule(); } - - /** - * 모임 정보를 수정합니다. - * - * @param requestDto 수정 요청 DTO - */ - public void updateMeeting(MeetingUpdateRequestDto requestDto) { - this.meetingName = requestDto.getMeetingName(); - this.meetingStartDate = requestDto.getMeetingStartDate(); - this.meetingEndDate = requestDto.getMeetingEndDate(); - this.numberOfPeople = requestDto.getNumberOfPeople(); - this.isOnline = requestDto.getIsOnline(); - this.isAnonymous = requestDto.getIsAnonymous(); - this.voteEndDate = requestDto.getVoteEndDate(); - } } diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java index fc38340..ee03367 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java @@ -1,7 +1,6 @@ package com.dnd.jjakkak.domain.meeting; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; -import com.dnd.jjakkak.domain.meeting.dto.request.MeetingUpdateRequestDto; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.entity.Meeting; import org.springframework.test.util.ReflectionTestUtils; @@ -103,18 +102,4 @@ public static List createMeetingList() { return List.of(meeting, study); } - public static MeetingUpdateRequestDto updateRequestDto(Long... categoryIds) { - MeetingUpdateRequestDto requestDto = new MeetingUpdateRequestDto(); - ReflectionTestUtils.setField(requestDto, "meetingName", "DND 11기 모임"); - ReflectionTestUtils.setField(requestDto, "meetingStartDate", LocalDate.of(2024, 8, 11)); - ReflectionTestUtils.setField(requestDto, "meetingEndDate", LocalDate.of(2024, 8, 12)); - ReflectionTestUtils.setField(requestDto, "numberOfPeople", 10); - ReflectionTestUtils.setField(requestDto, "isOnline", false); - ReflectionTestUtils.setField(requestDto, "isAnonymous", false); - ReflectionTestUtils.setField(requestDto, "voteEndDate", LocalDateTime.of(2024, 8, 4, 23, 59, 59)); - ReflectionTestUtils.setField(requestDto, "categoryIds", List.of(categoryIds)); - - return requestDto; - } - } From 408c01ee4b5a48e1c9ad0f95b00a431c71ba1f86 Mon Sep 17 00:00:00 2001 From: seungjo Date: Sat, 3 Aug 2024 15:08:55 +0900 Subject: [PATCH 18/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20-=20?= =?UTF-8?q?=EC=98=A8/=EC=98=A4=ED=94=84=EB=9D=BC=EC=9D=B8=20=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/meeting/dto/request/MeetingCreateRequestDto.java | 4 +--- .../domain/meeting/dto/response/MeetingResponseDto.java | 2 -- .../java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java | 6 +----- .../dnd/jjakkak/domain/meeting/service/MeetingService.java | 1 - .../java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java | 3 --- .../jjakkak/domain/meeting/service/MeetingServiceTest.java | 2 -- .../repository/MeetingCategoryRepositoryTest.java | 1 - 7 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java index 7b45d1f..ad337f4 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/request/MeetingCreateRequestDto.java @@ -28,7 +28,7 @@ public class MeetingCreateRequestDto { private final List categoryIds = new ArrayList<>(); @NotBlank(message = "모임명은 필수 값입니다.") - @Size(max = 30, message = "모임명을 30자 이내로 입력해주세요.") + @Size(max = 20, message = "모임명을 20자 이내로 입력해주세요.") private String meetingName; @NotNull(message = "모임 일정 시작일은 필수 값입니다.") @@ -41,8 +41,6 @@ public class MeetingCreateRequestDto { @Max(value = 10, message = "인원수는 10명 이하로 입력해주세요.") private Integer numberOfPeople; - private Boolean isOnline; - @NotNull(message = "익명 여부는 필수 값입니다.") private Boolean isAnonymous; diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java index 567320b..4a1b73a 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/dto/response/MeetingResponseDto.java @@ -21,7 +21,6 @@ public class MeetingResponseDto { private final LocalDate meetingStartDate; private final LocalDate meetingEndDate; private final Integer numberOfPeople; - private final Boolean isOnline; private final Boolean isAnonymous; private final LocalDateTime voteEndDate; private final LocalDateTime confirmedSchedule; @@ -35,7 +34,6 @@ public MeetingResponseDto(Meeting meeting) { this.meetingStartDate = meeting.getMeetingStartDate(); this.meetingEndDate = meeting.getMeetingEndDate(); this.numberOfPeople = meeting.getNumberOfPeople(); - this.isOnline = meeting.getIsOnline(); this.isAnonymous = meeting.getIsAnonymous(); this.voteEndDate = meeting.getVoteEndDate(); this.confirmedSchedule = meeting.getConfirmedSchedule(); diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java index 6f8c743..e03750e 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/entity/Meeting.java @@ -40,9 +40,6 @@ public class Meeting { @Column(nullable = false, name = "number_of_people") private Integer numberOfPeople; - @Column(name = "is_online") - private Boolean isOnline; - @Column(name = "is_anonymous") private Boolean isAnonymous; @@ -60,13 +57,12 @@ public class Meeting { @Builder public Meeting(String meetingName, LocalDate meetingStartDate, LocalDate meetingEndDate, - Integer numberOfPeople, Boolean isOnline, Boolean isAnonymous, + Integer numberOfPeople, Boolean isAnonymous, LocalDateTime voteEndDate, Long meetingLeaderId, String meetingUuid) { this.meetingName = meetingName; this.meetingStartDate = meetingStartDate; this.meetingEndDate = meetingEndDate; this.numberOfPeople = numberOfPeople; - this.isOnline = isOnline; this.isAnonymous = isAnonymous; this.voteEndDate = voteEndDate; this.meetingLeaderId = meetingLeaderId; diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index a245efb..b12ec8e 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -66,7 +66,6 @@ public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequest .meetingStartDate(requestDto.getMeetingStartDate()) .meetingEndDate(requestDto.getMeetingEndDate()) .numberOfPeople(requestDto.getNumberOfPeople()) - .isOnline(requestDto.getIsOnline()) .isAnonymous(requestDto.getIsAnonymous()) .voteEndDate(requestDto.getVoteEndDate()) .meetingLeaderId(member.getMemberId()) diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java index ee03367..2817b68 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java @@ -60,7 +60,6 @@ public static MeetingResponseDto createResponseDto() { .meetingStartDate(LocalDate.of(2024, 7, 27)) .meetingEndDate(LocalDate.of(2024, 7, 29)) .numberOfPeople(6) - .isOnline(true) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) .build(); @@ -84,7 +83,6 @@ public static List createMeetingList() { .meetingStartDate(LocalDate.of(2024, 7, 27)) .meetingEndDate(LocalDate.of(2024, 7, 29)) .numberOfPeople(6) - .isOnline(true) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) .build(); @@ -94,7 +92,6 @@ public static List createMeetingList() { .meetingStartDate(LocalDate.of(2024, 8, 1)) .meetingEndDate(LocalDate.of(2024, 8, 5)) .numberOfPeople(4) - .isOnline(true) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 30, 23, 59, 59)) .build(); diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java index 2481050..c25bf6a 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java @@ -99,7 +99,6 @@ void testGetMeeting_Success() { .meetingStartDate(LocalDate.of(2024, 7, 27)) .meetingEndDate(LocalDate.of(2024, 7, 29)) .numberOfPeople(6) - .isOnline(true) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) .meetingLeaderId(1L) @@ -118,7 +117,6 @@ void testGetMeeting_Success() { () -> assertEquals(actual.getMeetingStartDate(), meeting.getMeetingStartDate()), () -> assertEquals(actual.getMeetingEndDate(), meeting.getMeetingEndDate()), () -> assertEquals(actual.getNumberOfPeople(), meeting.getNumberOfPeople()), - () -> assertEquals(actual.getIsOnline(), meeting.getIsOnline()), () -> assertEquals(actual.getIsAnonymous(), meeting.getIsAnonymous()), () -> assertEquals(actual.getVoteEndDate(), meeting.getVoteEndDate()), () -> assertEquals(actual.getMeetingLeaderId(), meeting.getMeetingLeaderId()), diff --git a/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java b/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java index 9cb563f..ad45776 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meetingcategory/repository/MeetingCategoryRepositoryTest.java @@ -45,7 +45,6 @@ void testDeleteByMeetingId() { .meetingStartDate(LocalDate.of(2024, 7, 27)) .meetingEndDate(LocalDate.of(2024, 7, 29)) .numberOfPeople(6) - .isOnline(true) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) .meetingLeaderId(1L) From 7eaf5e0abe1083b19869109c1d22e595f375011a Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Sat, 3 Aug 2024 15:09:40 +0900 Subject: [PATCH 19/49] =?UTF-8?q?refactor:=20=ED=94=BC=EB=93=9C=EB=B0=B1?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/AccessTokenExpiredException.java | 16 ++++++++++++++++ .../jwt/exception/MalformedTokenException.java | 16 ++++++++++++++++ .../jwt/filter/JwtAuthenticationFilter.java | 16 +++++----------- .../member/jwt/handler/OAuth2FailureHandler.java | 2 +- .../member/jwt/handler/OAuth2LogoutHandler.java | 12 ++---------- .../global/config/security/SecurityConfig.java | 3 ++- 6 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java create mode 100644 src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java new file mode 100644 index 0000000..08fa067 --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java @@ -0,0 +1,16 @@ +package com.dnd.jjakkak.domain.member.jwt.exception; + +import com.dnd.jjakkak.global.exception.GeneralException; + +public class AccessTokenExpiredException extends GeneralException { + private static final String MESSAGE = "엑세스 토큰이 만료됨."; + + public AccessTokenExpiredException() { + super(MESSAGE); + } + + @Override + public int getStatusCode() { + return 401; + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java new file mode 100644 index 0000000..9f57d5b --- /dev/null +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java @@ -0,0 +1,16 @@ +package com.dnd.jjakkak.domain.member.jwt.exception; + +import com.dnd.jjakkak.global.exception.GeneralException; + +public class MalformedTokenException extends GeneralException { + private static final String MESSAGE = "손상된 토큰."; + + public MalformedTokenException() { + super(MESSAGE); + } + + @Override + public int getStatusCode() { + return 401; + } +} diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java index f5e6aa4..1844db6 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java @@ -3,6 +3,8 @@ import com.dnd.jjakkak.domain.member.entity.Member; import com.dnd.jjakkak.domain.member.entity.Role; import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; +import com.dnd.jjakkak.domain.member.jwt.exception.AccessTokenExpiredException; +import com.dnd.jjakkak.domain.member.jwt.exception.MalformedTokenException; import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import io.jsonwebtoken.ExpiredJwtException; @@ -31,7 +33,7 @@ /** * 검증을 실행하는 필터입니다. * @author 류태웅 - * @version 2024. 08. 02. + * @version 2024. 08. 03. */ @Slf4j @@ -55,18 +57,10 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse log.info("검증된 카카오 ID: {}", kakaoId); } catch (ExpiredJwtException e) { log.error("엑세스 토큰이 만료됨", e); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"Token expired\"}"); - response.getWriter().flush(); - return; + throw new AccessTokenExpiredException(); } catch (MalformedJwtException e) { log.error("손상된 토큰", e); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write("{\"error\": \"Malformed Token\"}"); - response.getWriter().flush(); - return; + throw new MalformedTokenException(); } if (kakaoId == null) { diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java index 0290b53..c4b4a24 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java @@ -25,7 +25,7 @@ public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { String errorMessage; - if (e instanceof UsernameNotFoundException){ + if(e instanceof UsernameNotFoundException){ errorMessage="존재하지 않는 아이디 입니다."; } else{ diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java index a078e89..2efd3c2 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java @@ -15,7 +15,8 @@ /** * 로그아웃 시 토큰에 빈 값을 넣어 전송 하는 방식으로 토큰을 삭제하는 핸들러입니다. - * + * @author 류태웅 + * @version 2024. 08. 03. */ @Slf4j @Component @@ -49,15 +50,6 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut LocalDateTime expirationDate = LocalDateTime.now().plusWeeks(1); blacklistService.blacklistToken(refreshToken, expirationDate); log.info("logout 성공"); - - Cookie refreshTokenCookie = new Cookie("refresh_token", null); - refreshTokenCookie.setPath("/"); - refreshTokenCookie.setHttpOnly(true); - refreshTokenCookie.setSecure(true); - refreshTokenCookie.setMaxAge(0); - - response.addCookie(refreshTokenCookie); - response.setStatus(HttpServletResponse.SC_OK); } catch (Exception e) { log.error("서버 에러", e); diff --git a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java index 09d7b29..6736e77 100644 --- a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java +++ b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java @@ -27,7 +27,7 @@ * Spring Security Configuration Class. * * @author 류태웅 - * @version 2024. 08. 02. + * @version 2024. 08. 03. */ @Configurable @@ -81,6 +81,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .logout(logout -> logout .addLogoutHandler(oAuth2LogoutHandler) .logoutUrl("/api/v1/logout") + .deleteCookies("refresh_token") .logoutSuccessHandler((request, response, authentication) -> response.setStatus(HttpServletResponse.SC_OK)) ) .exceptionHandling(exceptionHandling -> exceptionHandling // 실패 시 해당 메시지 반환 From b483cc7007fab1e1bd417c0b99b91c272a1d526c Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Sat, 3 Aug 2024 15:38:59 +0900 Subject: [PATCH 20/49] =?UTF-8?q?refactor:=20Member=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20Enum=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/dnd/jjakkak/domain/member/entity/Member.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/entity/Member.java b/src/main/java/com/dnd/jjakkak/domain/member/entity/Member.java index 1710470..ac172f4 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/entity/Member.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/entity/Member.java @@ -44,7 +44,7 @@ public class Member implements OAuth2User { private String memberProfile; @Enumerated(EnumType.STRING) - @Column(nullable = false, name="role") + @Column(nullable = false, name="role", columnDefinition = "varchar(20)") private Role role; /** From 00dc965f3e29966b8b48e8fd5cf9485c6f717800 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 15:01:59 +0900 Subject: [PATCH 21/49] refactor: SecurityContextHolder -> Member --- .../domain/member/jwt/filter/JwtAuthenticationFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java index 1844db6..c9274f8 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java @@ -32,6 +32,7 @@ /** * 검증을 실행하는 필터입니다. + * * @author 류태웅 * @version 2024. 08. 03. */ @@ -76,7 +77,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse authorities.add(new SimpleGrantedAuthority(role.toString())); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(kakaoId, null, authorities); + AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(member, null, authorities); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); securityContext.setAuthentication(authenticationToken); From 9438d9d46ca02693769d214eed3100f1385f784c Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 15:02:12 +0900 Subject: [PATCH 22/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/controller/MeetingController.java | 14 +++++++------- .../domain/meeting/service/MeetingService.java | 7 +++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java index 291bdc9..bf329b1 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java @@ -6,10 +6,12 @@ import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; +import com.dnd.jjakkak.domain.member.entity.Member; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -31,21 +33,19 @@ public class MeetingController { /** * 모임을 생성하는 메서드입니다. * - * @param accessToken JWT Token (Access Token) - * @param requestDto 모임 생성 요청 DTO + * @param member 로그인한 회원 정보 + * @param requestDto 모임 생성 요청 DTO * @return 201 (CREATED), body: 모임 생성 응답 DTO (UUID) */ @PostMapping - public ResponseEntity createGroup(@RequestHeader("Authorization") String accessToken, + public ResponseEntity createGroup(@AuthenticationPrincipal Member member, @Valid @RequestBody MeetingCreateRequestDto requestDto) { - if (Objects.isNull(accessToken)) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } + MeetingCreateResponseDto response = meetingService.createMeeting(member.getMemberId(), requestDto); return ResponseEntity .status(HttpStatus.CREATED) - .body(meetingService.createMeeting(accessToken, requestDto)); + .body(response); } /** diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index b12ec8e..6482c59 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -47,17 +47,16 @@ public class MeetingService { /** * 모임을 생성하는 메서드입니다. * - * @param token JWT Token + * @param memberId 모임을 생성하는 회원 ID (리더 ID) * @param requestDto 모임 생성 요청 DTO * @return 모임 생성 응답 DTO (UUID) */ @Transactional - public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequestDto requestDto) { + public MeetingCreateResponseDto createMeeting(Long memberId, MeetingCreateRequestDto requestDto) { // checkMeetingDate 메서드를 호출하여 유효성 검사를 진행합니다. requestDto.checkMeetingDate(); - Member member = getMemberByToken(token); String uuid = generateUuid(); // 모임 생성 로직 @@ -68,7 +67,7 @@ public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequest .numberOfPeople(requestDto.getNumberOfPeople()) .isAnonymous(requestDto.getIsAnonymous()) .voteEndDate(requestDto.getVoteEndDate()) - .meetingLeaderId(member.getMemberId()) + .meetingLeaderId(memberId) .meetingUuid(uuid) .build(); From 96e705b18f414d10599f75707f4611e391a4ae34 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 15:05:33 +0900 Subject: [PATCH 23/49] =?UTF-8?q?refactor:=20=EB=AA=A8=EC=9E=84=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/controller/MeetingController.java | 14 ++++------ .../meeting/service/MeetingService.java | 26 ++++--------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java index bf329b1..dc90f63 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java @@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Objects; /** * 모임 컨트롤러 클래스입니다. @@ -84,22 +83,19 @@ public ResponseEntity confirmMeeting(@PathVariable("meetingId") Long id, return ResponseEntity.ok().build(); } + /** * 모임을 삭제하는 메서드입니다. * - * @param accessToken JWT Token (Access Token) - * @param id 삭제할 모임 ID + * @param member 로그인한 회원 정보 + * @param id 삭제할 모임 ID * @return 200 (OK) */ @DeleteMapping("/{meetingId}") - public ResponseEntity deleteMeeting(@RequestHeader("Authorization") String accessToken, + public ResponseEntity deleteMeeting(@AuthenticationPrincipal Member member, @PathVariable("meetingId") Long id) { - if (Objects.isNull(accessToken)) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - - meetingService.deleteMeeting(accessToken, id); + meetingService.deleteMeeting(member.getMemberId(), id); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index 6482c59..e80ba0c 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -16,7 +16,6 @@ import com.dnd.jjakkak.domain.meetingmember.repository.MeetingMemberRepository; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; @@ -24,7 +23,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Objects; import java.util.UUID; /** @@ -126,18 +124,18 @@ public void confirmMeeting(Long id, MeetingConfirmRequestDto requestDto) { /** * 모임을 삭제하는 메서드입니다. * - * @param token JWT Token - * @param id 모임 ID + * @param memberId 회원 ID + * @param id 모임 ID */ @Transactional - public void deleteMeeting(String token, Long id) { + public void deleteMeeting(Long memberId, Long id) { - Member member = getMemberByToken(token); Meeting meeting = meetingRepository.findById(id) .orElseThrow(MeetingNotFoundException::new); - if (!meeting.getMeetingLeaderId().equals(member.getMemberId())) { + // 요청한 회원이 모임의 리더가 아닌 경우 예외 처리 + if (!meeting.getMeetingLeaderId().equals(memberId)) { throw new MeetingUnauthorizedException(); } @@ -176,18 +174,4 @@ private String generateUuid() { return uuid; } - - /** - * 토큰을 통해 회원 정보를 조회하는 메서드입니다. - * - * @param token AccessToken - * @return Member Entity - */ - private Member getMemberByToken(String token) { - String kakaoId = Objects.requireNonNull(jwtProvider.validate(token)); - - return memberRepository.findByKakaoId(Long.parseLong(kakaoId)) - .orElseThrow(MemberNotFoundException::new); - } - } From 7082a396042828065d5a93bd274a23dbea8e27e6 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 15:08:26 +0900 Subject: [PATCH 24/49] =?UTF-8?q?refactor:=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20(JwtProvider,=20MemberRepository)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../meeting/service/MeetingService.java | 4 -- .../meeting/service/MeetingServiceTest.java | 41 ++----------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index e80ba0c..608da8a 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -16,8 +16,6 @@ import com.dnd.jjakkak.domain.meetingmember.repository.MeetingMemberRepository; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -39,8 +37,6 @@ public class MeetingService { private final MeetingCategoryRepository meetingCategoryRepository; private final CategoryRepository categoryRepository; private final MeetingMemberRepository meetingMemberRepository; - private final JwtProvider jwtProvider; - private final MemberRepository memberRepository; /** * 모임을 생성하는 메서드입니다. diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java index c25bf6a..73bfda4 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java @@ -9,16 +9,12 @@ import com.dnd.jjakkak.domain.meeting.exception.MeetingNotFoundException; import com.dnd.jjakkak.domain.meeting.repository.MeetingRepository; import com.dnd.jjakkak.domain.meetingcategory.repository.MeetingCategoryRepository; -import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.repository.MemberRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; import java.time.LocalDate; import java.time.LocalDateTime; @@ -49,12 +45,6 @@ class MeetingServiceTest { @Mock MeetingCategoryRepository meetingCategoryRepository; - @Mock - JwtProvider jwtProvider; - - @Mock - MemberRepository memberRepository; - @Test @DisplayName("모임 생성 테스트 - 성공") void testCreateMeeting() { @@ -69,18 +59,11 @@ void testCreateMeeting() { .categoryName("회의") .build(); - Member member = Member.builder() - .kakaoId(1L) - .memberNickname("seungjo") - .build(); - when(categoryRepository.findById(1L)).thenReturn(Optional.of(teamProject)); when(categoryRepository.findById(2L)).thenReturn(Optional.of(meeting)); - when(jwtProvider.validate(anyString())).thenReturn("1"); - when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); // when - meetingService.createMeeting("access_token", actual); + meetingService.createMeeting(1L, actual); // then verify(meetingRepository, times(1)).save(any()); @@ -149,19 +132,10 @@ void testDeleteMeeting_Success() { .meetingLeaderId(1L) .build(); - Member member = Member.builder() - .kakaoId(1L) - .memberNickname("seungjo") - .build(); - - ReflectionTestUtils.setField(member, "memberId", 1L); - - when(jwtProvider.validate(anyString())).thenReturn("1"); - when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); when(meetingRepository.findById(anyLong())).thenReturn(Optional.of(meeting)); // when - meetingService.deleteMeeting("access_token", 1L); + meetingService.deleteMeeting(1L, 1L); // then verify(meetingRepository, times(1)).deleteById(1L); @@ -171,18 +145,9 @@ void testDeleteMeeting_Success() { @Test @DisplayName("모임 삭제 테스트 - 실패 (존재하지 않는 모임)") void testDeleteMeeting_Fail() { - // given - Member member = Member.builder() - .kakaoId(1L) - .memberNickname("seungjo") - .build(); - - - when(jwtProvider.validate(anyString())).thenReturn("1"); - when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); // expected assertThrows(MeetingNotFoundException.class, - () -> meetingService.deleteMeeting("invalid_token", 1L)); + () -> meetingService.deleteMeeting(1L, 1L)); } } \ No newline at end of file From 48a3201825d2210b827dacb64191827829c30a45 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 15:08:38 +0900 Subject: [PATCH 25/49] =?UTF-8?q?test:=20=EB=8D=94=EB=AF=B8=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java index 2817b68..c4f5537 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java @@ -31,7 +31,6 @@ public static MeetingCreateRequestDto createRequestDto(List categoryIds) { ReflectionTestUtils.setField(requestDto, "meetingStartDate", LocalDate.of(2024, 7, 27)); ReflectionTestUtils.setField(requestDto, "meetingEndDate", LocalDate.of(2024, 7, 29)); ReflectionTestUtils.setField(requestDto, "numberOfPeople", 6); - ReflectionTestUtils.setField(requestDto, "isOnline", true); ReflectionTestUtils.setField(requestDto, "isAnonymous", false); ReflectionTestUtils.setField(requestDto, "voteEndDate", LocalDateTime.of(2024, 7, 26, 23, 59, 59)); ReflectionTestUtils.setField(requestDto, "categoryIds", categoryIds); From b9942afdd65cc03f4ef3456142bddf9b0746a93a Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 16:53:27 +0900 Subject: [PATCH 26/49] test: Security Mock User --- .../config/JjakkakMockSecurityContext.java | 44 +++++++++++++++++++ .../dnd/jjakkak/config/JjakkakMockUser.java | 21 +++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java create mode 100644 src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java diff --git a/src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java b/src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java new file mode 100644 index 0000000..0b95241 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java @@ -0,0 +1,44 @@ +package com.dnd.jjakkak.config; + +import com.dnd.jjakkak.domain.member.entity.Member; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +import java.util.List; + +/** + * 테스트에서 사용할 MockUser 어노테이션에 SecurityContext 값을 설정하는 클래스입니다. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@RequiredArgsConstructor +public class JjakkakMockSecurityContext implements WithSecurityContextFactory { + + private final MemberRepository memberRepository; + + + @Override + public SecurityContext createSecurityContext(JjakkakMockUser annotation) { + + Member member = Member.builder() + .memberNickname(annotation.nickname()) + .kakaoId(annotation.kakaoId()) + .build(); + + memberRepository.save(member); + + SimpleGrantedAuthority role = new SimpleGrantedAuthority("ROLE_USER"); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(member, null, List.of(role)); + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authToken); + + return context; + } +} diff --git a/src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java b/src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java new file mode 100644 index 0000000..928322d --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java @@ -0,0 +1,21 @@ +package com.dnd.jjakkak.config; + +import org.springframework.security.test.context.support.WithSecurityContext; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * MockUser 어노테이션입니다. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = JjakkakMockSecurityContext.class) +public @interface JjakkakMockUser { + + String nickname() default "seungjo"; + + long kakaoId() default 1234567890; +} From 6527440a82a9e764fc4c085742b1212edec669be Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 16:53:42 +0900 Subject: [PATCH 27/49] chore: lombok (test) --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 930cfce..5aef1cc 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,10 @@ dependencies { // Spring REST Docs asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + + // test lombok + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { From 224f6d6f273ac047d61da8d33292c0ee0d77ca97 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 16:54:56 +0900 Subject: [PATCH 28/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MeetingControllerTest.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java new file mode 100644 index 0000000..5c1d584 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java @@ -0,0 +1,84 @@ +package com.dnd.jjakkak.domain.meeting.controller; + +import com.dnd.jjakkak.config.JjakkakMockUser; +import com.dnd.jjakkak.domain.meeting.MeetingDummy; +import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; +import com.dnd.jjakkak.domain.meeting.service.MeetingService; +import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.List; + +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * {class name}. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@AutoConfigureRestDocs +@WebMvcTest(MeetingController.class) +class MeetingControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + MeetingService meetingService; + + @MockBean + JwtProvider jwtProvider; + + @MockBean + MemberRepository memberRepository; + + @Autowired + ObjectMapper objectMapper; + + @BeforeEach + public void setUp(WebApplicationContext webApplicationContext) { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .apply(springSecurity()) + .defaultRequest(post("/**").with(csrf())) + .build(); + + } + + @Test + @DisplayName("모임 생성 테스트 - 성공") + @JjakkakMockUser + void 모임생성_성공() throws Exception { + + MeetingCreateRequestDto requestDto = MeetingDummy.createRequestDto(List.of(1L, 2L)); + + objectMapper.writeValueAsString(requestDto); + + String token = "Bearer access_token"; + + mockMvc.perform(post("/api/v1/meeting") + .header("Authorization", token) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestDto))) + .andExpectAll(status().isCreated()) + .andDo(print()); + } + +} \ No newline at end of file From 27414ff0c0db5fd8ce99a067ad7d2e110c000619 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Mon, 5 Aug 2024 17:16:17 +0900 Subject: [PATCH 29/49] =?UTF-8?q?chore=20:=20JWT=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/exception/AccessTokenExpiredException.java | 2 +- .../jwt/exception/MalformedTokenException.java | 2 +- .../{member => }/jwt/filter/JwtAuthenticationFilter.java | 8 ++++---- .../{member => }/jwt/handler/OAuth2FailureHandler.java | 2 +- .../{member => }/jwt/handler/OAuth2LogoutHandler.java | 2 +- .../{member => }/jwt/handler/OAuth2SuccessHandler.java | 4 ++-- .../domain/{member => }/jwt/provider/JwtProvider.java | 2 +- .../jjakkak/domain/meeting/service/MeetingService.java | 2 +- .../jjakkak/domain/member/controller/AuthController.java | 2 +- .../jjakkak/global/config/security/SecurityConfig.java | 8 ++++---- .../domain/meeting/service/MeetingServiceTest.java | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/exception/AccessTokenExpiredException.java (86%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/exception/MalformedTokenException.java (86%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/filter/JwtAuthenticationFilter.java (93%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/handler/OAuth2FailureHandler.java (96%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/handler/OAuth2LogoutHandler.java (98%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/handler/OAuth2SuccessHandler.java (95%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/provider/JwtProvider.java (98%) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/AccessTokenExpiredException.java similarity index 86% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/exception/AccessTokenExpiredException.java index 08fa067..4dd34fb 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/AccessTokenExpiredException.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.exception; +package com.dnd.jjakkak.domain.jwt.exception; import com.dnd.jjakkak.global.exception.GeneralException; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/MalformedTokenException.java similarity index 86% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/exception/MalformedTokenException.java index 9f57d5b..759aa7a 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/MalformedTokenException.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.exception; +package com.dnd.jjakkak.domain.jwt.exception; import com.dnd.jjakkak.global.exception.GeneralException; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java similarity index 93% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java index 1844db6..58902e6 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java @@ -1,11 +1,11 @@ -package com.dnd.jjakkak.domain.member.jwt.filter; +package com.dnd.jjakkak.domain.jwt.filter; import com.dnd.jjakkak.domain.member.entity.Member; import com.dnd.jjakkak.domain.member.entity.Role; import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; -import com.dnd.jjakkak.domain.member.jwt.exception.AccessTokenExpiredException; -import com.dnd.jjakkak.domain.member.jwt.exception.MalformedTokenException; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.jwt.exception.AccessTokenExpiredException; +import com.dnd.jjakkak.domain.jwt.exception.MalformedTokenException; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2FailureHandler.java similarity index 96% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2FailureHandler.java index c4b4a24..516c1e3 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2FailureHandler.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.handler; +package com.dnd.jjakkak.domain.jwt.handler; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java similarity index 98% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java index 2efd3c2..93b7530 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.handler; +package com.dnd.jjakkak.domain.jwt.handler; import com.dnd.jjakkak.domain.member.service.BlacklistService; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java similarity index 95% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java index a2b4172..b654e0b 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java @@ -1,7 +1,7 @@ -package com.dnd.jjakkak.domain.member.jwt.handler; +package com.dnd.jjakkak.domain.jwt.handler; import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java b/src/main/java/com/dnd/jjakkak/domain/jwt/provider/JwtProvider.java similarity index 98% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/provider/JwtProvider.java index 42f594a..37c19c1 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/provider/JwtProvider.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.provider; +package com.dnd.jjakkak.domain.jwt.provider; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index b12ec8e..4149405 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -17,7 +17,7 @@ import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; import com.dnd.jjakkak.domain.member.entity.Member; import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java index ecddf46..3420527 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java @@ -1,6 +1,6 @@ package com.dnd.jjakkak.domain.member.controller; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java index 6736e77..5b01968 100644 --- a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java +++ b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java @@ -1,9 +1,9 @@ package com.dnd.jjakkak.global.config.security; -import com.dnd.jjakkak.domain.member.jwt.filter.JwtAuthenticationFilter; -import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2FailureHandler; -import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2LogoutHandler; -import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2SuccessHandler; +import com.dnd.jjakkak.domain.jwt.filter.JwtAuthenticationFilter; +import com.dnd.jjakkak.domain.jwt.handler.OAuth2FailureHandler; +import com.dnd.jjakkak.domain.jwt.handler.OAuth2LogoutHandler; +import com.dnd.jjakkak.domain.jwt.handler.OAuth2SuccessHandler; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Configurable; diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java index c25bf6a..0d2fe4a 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java @@ -10,7 +10,7 @@ import com.dnd.jjakkak.domain.meeting.repository.MeetingRepository; import com.dnd.jjakkak.domain.meetingcategory.repository.MeetingCategoryRepository; import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 936b9e4765a76cc7d7913bd157485d2b8ae3f46b Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Mon, 5 Aug 2024 17:59:32 +0900 Subject: [PATCH 30/49] =?UTF-8?q?feat:=20=EA=B0=81=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EB=B3=84=20=EC=A0=91=EA=B7=BC=20=EA=B0=80=EB=8A=A5=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/filter/JwtAuthenticationFilter.java | 7 ++++++ .../member/controller/AuthController.java | 2 +- .../config/security/SecurityConfig.java | 22 +++++++++++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java index 58902e6..c451c07 100644 --- a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java @@ -7,6 +7,7 @@ import com.dnd.jjakkak.domain.jwt.exception.MalformedTokenException; import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; +import com.dnd.jjakkak.global.config.security.SecurityConfig; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import jakarta.servlet.FilterChain; @@ -23,6 +24,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; +import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -45,6 +47,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String path = request.getRequestURI(); + if(PatternMatchUtils.simpleMatch(SecurityConfig.WHITE_LIST, path)){ + log.info("path: {} -> passed token filter", path); + filterChain.doFilter(request, response); + } String token = parseBearerToken(request); log.info("도착한 토큰: {}", token); if (token == null) { // Bearer 인증 방식이 아니거나 빈 값일 경우 진행하지 말고 다음 필터로 바로 넘김 diff --git a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java index 3420527..14216a1 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java @@ -15,7 +15,7 @@ * 현재 Member의 로그인 여부를 확인하는 컨트롤러입니다. * * @author 류태웅 - * @version 2024. 08. 02. + * @version 2024. 08. 05. * */ @Slf4j diff --git a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java index 5b01968..4f48453 100644 --- a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java +++ b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java @@ -42,13 +42,28 @@ public class SecurityConfig { private final OAuth2FailureHandler oAuth2FailureHandler; private final OAuth2LogoutHandler oAuth2LogoutHandler; + public static final String[] WHITE_LIST = { + "/api/v1/auth/oauth/**", + "/api/v1/check-auth", + "/api/v1/meeting" + }; + + public static final String[] USER_LIST = { + "/api/v1/categories", + "/api/v1/member/**" + }; + + public static final String[] ADMIN_LIST = { + + }; + /** * Security Bean 등록. * *
  • CSRF 비활성화
  • *
  • CORS 비활성화 -> corsConfigurationSource()로 설정
  • *
  • Form Login 비활성화
  • - *
  • 모든 요청 허용 -> 추후에 변경 필요 -> USER와 ADMIN에 따라 결정해야 할 듯? (류태웅)
  • + *
  • 비회원도 접근 가능한 whiteList, 회원만 접근 가능한 userList, 관리자만 접근 가능한 adminList
  • * *
  • httpBasic 비활성화
  • *
  • 세션 비활성화
  • @@ -66,7 +81,10 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) .formLogin(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorize -> authorize - .anyRequest().permitAll() + .requestMatchers(WHITE_LIST).permitAll() + .requestMatchers(USER_LIST).hasRole("USER") + .requestMatchers(ADMIN_LIST).hasRole("ADMIN") + .anyRequest().authenticated() ) .sessionManagement(sessionManagement -> sessionManagement .sessionCreationPolicy(SessionCreationPolicy.STATELESS) From ab9f23067661df373570c95e7eb15af091b563c4 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 23:22:01 +0900 Subject: [PATCH 31/49] =?UTF-8?q?test:=20RestDocs=20Configuration=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jjakkak/config/AbstractRestDocsTest.java | 54 +++++++++++++++++++ .../jjakkak/config/RestDocsConfiguration.java | 26 +++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java create mode 100644 src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java diff --git a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java new file mode 100644 index 0000000..044829f --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java @@ -0,0 +1,54 @@ +package com.dnd.jjakkak.config; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + +/** + * {class name}. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +public abstract class AbstractRestDocsTest { + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected MockMvc mockMvc; + + @BeforeEach + void setUp(WebApplicationContext context, RestDocumentationContextProvider restDocumentation) { + + + this.mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(documentationConfiguration(restDocumentation)) + .alwaysDo(MockMvcResultHandlers.print()) + .alwaysDo(restDocs) + .addFilters(new CharacterEncodingFilter("UTF-8", true)) + .apply(springSecurity()) + .defaultRequest(post("/**").with(csrf())) + .defaultRequest(get("/**").with(csrf())) + .defaultRequest(patch("/**").with(csrf())) + .defaultRequest(delete("/**").with(csrf())) + .build(); + } +} diff --git a/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java b/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java new file mode 100644 index 0000000..d5d65a0 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java @@ -0,0 +1,26 @@ +package com.dnd.jjakkak.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.operation.preprocess.Preprocessors; + +/** + * {class name}. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@TestConfiguration +public class RestDocsConfiguration { + + @Bean + public RestDocumentationResultHandler write() { + return MockMvcRestDocumentation.document( + "{class-name}/{method-name}", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()) + ); + } +} From e5e2ffc2611e37ef6b9a8b550ccd53a5a5cbe2a5 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 23:29:41 +0900 Subject: [PATCH 32/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MeetingControllerTest.java | 87 ++++++++++++------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java index 5c1d584..291e4ad 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java @@ -1,5 +1,6 @@ package com.dnd.jjakkak.domain.meeting.controller; +import com.dnd.jjakkak.config.AbstractRestDocsTest; import com.dnd.jjakkak.config.JjakkakMockUser; import com.dnd.jjakkak.domain.meeting.MeetingDummy; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; @@ -7,7 +8,7 @@ import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -15,30 +16,23 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; import java.util.List; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.snippet.Attributes.key; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** - * {class name}. + * 모임 컨트롤러 테스트입니다. * * @author 정승조 * @version 2024. 08. 05. */ -@AutoConfigureRestDocs @WebMvcTest(MeetingController.class) -class MeetingControllerTest { - - @Autowired - MockMvc mockMvc; +@AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) +class MeetingControllerTest extends AbstractRestDocsTest { @MockBean MeetingService meetingService; @@ -50,35 +44,70 @@ class MeetingControllerTest { MemberRepository memberRepository; @Autowired - ObjectMapper objectMapper; + ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); - @BeforeEach - public void setUp(WebApplicationContext webApplicationContext) { - this.mockMvc = MockMvcBuilders - .webAppContextSetup(webApplicationContext) - .apply(springSecurity()) - .defaultRequest(post("/**").with(csrf())) - .build(); - - } @Test @DisplayName("모임 생성 테스트 - 성공") @JjakkakMockUser - void 모임생성_성공() throws Exception { + void create_success() throws Exception { MeetingCreateRequestDto requestDto = MeetingDummy.createRequestDto(List.of(1L, 2L)); - - objectMapper.writeValueAsString(requestDto); + String json = objectMapper.writeValueAsString(requestDto); String token = "Bearer access_token"; mockMvc.perform(post("/api/v1/meeting") .header("Authorization", token) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(requestDto))) - .andExpectAll(status().isCreated()) - .andDo(print()); + .content(json)) + .andExpect(status().isCreated()) + .andDo(restDocs.document( + requestFields( + fieldWithPath("meetingName").description("모임 이름") + .attributes(key("constraint").value("모임 이름은 1자 이상 10자 이하로 입력해주세요.")), + fieldWithPath("meetingStartDate").description("모임 시작 날짜") + .attributes(key("constraint").value("모임 시작일은 종료일 이전이어야 합니다.")), + fieldWithPath("meetingEndDate").description("모임 종료 날짜") + .attributes(key("constraint").value("모임 종료일은 시작일 이후이어야 합니다.")), + fieldWithPath("numberOfPeople").description("모임 인원") + .attributes(key("constraint").value("모임 인원은 2명 이상 10명 이하로 설정해주세요.")), + fieldWithPath("isAnonymous").description("익명 여부") + .attributes(key("constraint").value("default = false (실명)")), + fieldWithPath("voteEndDate").description("투표 종료 날짜") + .attributes(key("constraint").value("투표 종료일은 모임 시작일 이전이어야 합니다.")), + fieldWithPath("categoryIds").description("카테고리 아이디 목록") + .attributes(key("constraint").value("1개 이상의 카테고리를 선택해주세요.")) + ))); } + @Test + @DisplayName("모임 생성 테스트 - 실패 (Invalid)") + @JjakkakMockUser + void create_fail_invalid() throws Exception { + MeetingCreateRequestDto requestDto = MeetingDummy.createInvalidRequestDto(); + String json = objectMapper.writeValueAsString(requestDto); + + String token = "Bearer access_token"; + + mockMvc.perform(post("/api/v1/meeting") + .header("Authorization", token) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(restDocs.document( + responseFields( + fieldWithPath("code").description("상태 코드"), + fieldWithPath("message").description("에러 메시지"), + fieldWithPath("validation").description("유효성 검사 오류 목록"), + fieldWithPath("validation.meetingName").description("모임명은 필수 값입니다."), + fieldWithPath("validation.meetingStartDate").description("모임 일정 시작일은 필수 값입니다."), + fieldWithPath("validation.meetingEndDate").description("모임 일정 종료일은 필수 값입니다."), + fieldWithPath("validation.numberOfPeople").description("인원수는 필수 값입니다."), + fieldWithPath("validation.isAnonymous").description("익명 여부는 필수 값입니다."), + fieldWithPath("validation.voteEndDate").description("투표 종료일은 필수 값입니다."), + fieldWithPath("validation.categoryIds").description("카테고리는 최소 1개 이상 8개 이하로 선택해주세요.") + ))); + + } } \ No newline at end of file From 3259d3a35613812c0a00d45e328e18cf6d5c6f49 Mon Sep 17 00:00:00 2001 From: seungjo Date: Mon, 5 Aug 2024 23:32:29 +0900 Subject: [PATCH 33/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20asciidoc=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api/meeting.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/docs/asciidoc/api/meeting.adoc b/src/docs/asciidoc/api/meeting.adoc index c153d80..da4152d 100644 --- a/src/docs/asciidoc/api/meeting.adoc +++ b/src/docs/asciidoc/api/meeting.adoc @@ -3,24 +3,24 @@ === 모임 생성 - 성공 .HTTP Request -include::{snippets}/meeting/create/success/http-request.adoc[] +include::{snippets}/meeting-controller-test/create_success/http-request.adoc[] .Request Fields -include::{snippets}/meeting/create/success/request-fields.adoc[] +include::{snippets}/meeting-controller-test/create_success/request-fields.adoc[] .HTTP Response -include::{snippets}/meeting/create/success/http-response.adoc[] +include::{snippets}/meeting-controller-test/create_success/http-response.adoc[] === 모임 생성 - 실패 .HTTP Request -include::{snippets}/meeting/create/fail/http-request.adoc[] +include::{snippets}/meeting-controller-test/create_fail_invalid/http-request.adoc[] .HTTP Response -include::{snippets}/meeting/create/fail/http-response.adoc[] +include::{snippets}/meeting-controller-test/create_fail_invalid/http-response.adoc[] .Error Response -include::{snippets}/meeting/create/fail/response-fields.adoc[] +include::{snippets}/meeting-controller-test/create_fail_invalid/response-fields.adoc[] === 모임 전체 조회 From 7d7766c5a21ddc287f392dfbcdc721a5c0739e55 Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 01:19:07 +0900 Subject: [PATCH 34/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20REST=20Docs=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api/meeting.adoc | 69 +++----------- .../jjakkak/domain/meeting/MeetingDummy.java | 2 + .../controller/MeetingControllerTest.java | 93 ++++++++++++++++++- 3 files changed, 107 insertions(+), 57 deletions(-) diff --git a/src/docs/asciidoc/api/meeting.adoc b/src/docs/asciidoc/api/meeting.adoc index da4152d..29e2372 100644 --- a/src/docs/asciidoc/api/meeting.adoc +++ b/src/docs/asciidoc/api/meeting.adoc @@ -22,76 +22,33 @@ include::{snippets}/meeting-controller-test/create_fail_invalid/http-response.ad .Error Response include::{snippets}/meeting-controller-test/create_fail_invalid/response-fields.adoc[] -=== 모임 전체 조회 +=== 모임 조회 (UUID) -.Response Fields -include::{snippets}/meeting/getList/success/response-fields.adoc[] +include::{snippets}/meeting-controller-test/get_by-uuid/path-parameters.adoc[] .HTTP Request -include::{snippets}/meeting/getList/success/http-request.adoc[] +include::{snippets}/meeting-controller-test/get_by-uuid/http-request.adoc[] .HTTP Response -include::{snippets}/meeting/getList/success/http-response.adoc[] +include::{snippets}/meeting-controller-test/get_by-uuid/http-response.adoc[] -=== 모임 개별 조회 - 성공 +include::{snippets}/meeting-controller-test/get_by-uuid/response-fields.adoc[] -include::{snippets}/meeting/get/success/path-parameters.adoc[] +=== 모임 확정 일정 수정 .HTTP Request -include::{snippets}/meeting/get/success/http-request.adoc[] +include::{snippets}/meeting-controller-test/update_confirmed-schedule/http-request.adoc[] +include::{snippets}/meeting-controller-test/update_confirmed-schedule/request-fields.adoc[] .HTTP Response -include::{snippets}/meeting/get/success/http-response.adoc[] +include::{snippets}/meeting-controller-test/update_confirmed-schedule/http-response.adoc[] -=== 모임 개별 조회 - 실패 - -include::{snippets}/meeting/get/fail/path-parameters.adoc[] - -.HTTP Request -include::{snippets}/meeting/get/fail/http-request.adoc[] - -include::{snippets}/meeting/get/fail/http-response.adoc[] - -=== 모임 수정 - 성공 - -include::{snippets}/meeting/update/success/path-parameters.adoc[] - -.HTTP Request -include::{snippets}/meeting/update/success/http-request.adoc[] - - -.Request Fields -include::{snippets}/meeting/update/success/request-fields.adoc[] - - - -.HTTP Response -include::{snippets}/meeting/update/success/http-response.adoc[] - -=== 모임 수정 - 실패 - -.HTTP Request -include::{snippets}/meeting/update/fail/http-request.adoc[] - -.HTTP Response -include::{snippets}/meeting/update/fail/http-response.adoc[] - -=== 모임 삭제 - 성공 - -include::{snippets}/meeting/delete/success/path-parameters.adoc[] -.HTTP Request -include::{snippets}/meeting/delete/success/http-request.adoc[] - -.HTTP Response -include::{snippets}/meeting/delete/success/http-response.adoc[] - -=== 모임 삭제 - 실패 - -include::{snippets}/meeting/delete/fail/path-parameters.adoc[] +=== 모임 삭제 .HTTP Request -include::{snippets}/meeting/delete/fail/http-request.adoc[] +include::{snippets}/meeting-controller-test/delete_success/path-parameters.adoc[] +include::{snippets}/meeting-controller-test/delete_success/http-request.adoc[] .HTTP Response -include::{snippets}/meeting/delete/fail/http-response.adoc[] +include::{snippets}/meeting-controller-test/delete_success/http-response.adoc[] diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java index c4f5537..e2cfc68 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java @@ -61,6 +61,8 @@ public static MeetingResponseDto createResponseDto() { .numberOfPeople(6) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) + .meetingLeaderId(1L) + .meetingUuid("1234ABCD") .build(); ReflectionTestUtils.setField(meeting, "meetingId", 1L); diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java index 291e4ad..0eaff7a 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java @@ -3,6 +3,7 @@ import com.dnd.jjakkak.config.AbstractRestDocsTest; import com.dnd.jjakkak.config.JjakkakMockUser; import com.dnd.jjakkak.domain.meeting.MeetingDummy; +import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; @@ -16,12 +17,19 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; +import java.time.LocalDateTime; import java.util.List; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -110,4 +118,87 @@ void create_fail_invalid() throws Exception { ))); } + + @Test + @DisplayName("모임 조회 테스트") + @JjakkakMockUser + void get_byUuid() throws Exception { + + // given + String uuid = "1234ABCD"; + when(meetingService.getMeetingByUuid(anyString())).thenReturn(MeetingDummy.createResponseDto()); + + // expected + + mockMvc.perform(get("/api/v1/meeting/{meetingUuid}", uuid) + .contentType(MediaType.APPLICATION_JSON)) + .andExpectAll( + status().isOk(), + jsonPath("$.meetingId").value(1L), + jsonPath("$.meetingName").value("세븐일레븐"), + jsonPath("$.meetingStartDate").value("2024-07-27"), + jsonPath("$.meetingEndDate").value("2024-07-29"), + jsonPath("$.numberOfPeople").value(6), + jsonPath("$.isAnonymous").value(false), + jsonPath("$.voteEndDate").value("2024-07-26T23:59:59"), + jsonPath("$.confirmedSchedule").doesNotExist(), // null + jsonPath("$.meetingLeaderId").value(1L), + jsonPath("$.meetingUuid").value("1234ABCD") + ) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingUuid").description("모임 UUID")), + responseFields( + fieldWithPath("meetingId").description("모임 ID"), + fieldWithPath("meetingName").description("모임 이름"), + fieldWithPath("meetingStartDate").description("모임 시작 날짜"), + fieldWithPath("meetingEndDate").description("모임 종료 날짜"), + fieldWithPath("numberOfPeople").description("모임 인원"), + fieldWithPath("isAnonymous").description("익명 여부"), + fieldWithPath("voteEndDate").description("투표 종료 날짜"), + fieldWithPath("confirmedSchedule").description("확정된 일정"), + fieldWithPath("meetingLeaderId").description("모임 리더 ID"), + fieldWithPath("meetingUuid").description("모임 UUID") + ))); + } + + @Test + @JjakkakMockUser + @DisplayName("모임 확정 일정 수정 테스트") + void update_confirmedSchedule() throws Exception { + + // given + MeetingConfirmRequestDto requestDto = new MeetingConfirmRequestDto(); + ReflectionTestUtils.setField(requestDto, "confirmedSchedule", LocalDateTime.of(2024, 7, 28, 12, 30)); + + String json = objectMapper.writeValueAsString(requestDto); + + // expected + mockMvc.perform(patch("/api/v1/meeting/{meetingId}/confirm", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingId").description("모임 ID")), + requestFields( + fieldWithPath("confirmedSchedule").description("확정된 일정") + .attributes(key("constraint").value("확정된 일정은 시작일과 종료일 사이의 날짜여야 합니다."))) + )); + } + + @Test + @DisplayName("모임 삭제 테스트") + @JjakkakMockUser + void delete_success() throws Exception { + + // expected + mockMvc.perform(delete("/api/v1/meeting/{meetingId}", 1L) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingId").description("모임 ID")) + )); + } } \ No newline at end of file From bf55135b73f8770fa6af26f5e4a6a4785e492616 Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 01:19:25 +0900 Subject: [PATCH 35/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20REST=20Docs=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jjakkak/domain/meeting/MeetingDummy.java | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java index e2cfc68..33d71d9 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java @@ -72,32 +72,4 @@ public static MeetingResponseDto createResponseDto() { .build(); } - /** - * Meeting 엔티티를 생성하여 반환합니다. - * - * @return Meeting 엔티티 리스트 - */ - public static List createMeetingList() { - - Meeting meeting = Meeting.builder() - .meetingName("DND 7조 회의") - .meetingStartDate(LocalDate.of(2024, 7, 27)) - .meetingEndDate(LocalDate.of(2024, 7, 29)) - .numberOfPeople(6) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) - .build(); - - Meeting study = Meeting.builder() - .meetingName("Java 스터디") - .meetingStartDate(LocalDate.of(2024, 8, 1)) - .meetingEndDate(LocalDate.of(2024, 8, 5)) - .numberOfPeople(4) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 30, 23, 59, 59)) - .build(); - - return List.of(meeting, study); - } - } From cc4f18456956089ba6d861d1297f06b3fd93d12b Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 01:25:06 +0900 Subject: [PATCH 36/49] =?UTF-8?q?test:=20REST=20Docs=20Configuration=20jav?= =?UTF-8?q?adoc=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java | 2 +- src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java index 044829f..29d06e3 100644 --- a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java +++ b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java @@ -19,7 +19,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; /** - * {class name}. + * REST Docs 설정을 적용한 테스트 클래스입니다. (REST Docs 최상위 클래스) * * @author 정승조 * @version 2024. 08. 05. diff --git a/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java b/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java index d5d65a0..0583ecd 100644 --- a/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java +++ b/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java @@ -7,7 +7,7 @@ import org.springframework.restdocs.operation.preprocess.Preprocessors; /** - * {class name}. + * REST Docs 설정 클래스입니다. * * @author 정승조 * @version 2024. 08. 05. From 5ff2a90c1f0e1cb1ff4cb9aff6e7ae3851bb44a5 Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 13:29:07 +0900 Subject: [PATCH 37/49] =?UTF-8?q?test:=20JWT=20Filter=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20Bean=EC=9D=84=20Abstract?= =?UTF-8?q?=20Test=20Class=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/jjakkak/config/AbstractRestDocsTest.java | 13 +++++++++++++ .../meeting/controller/MeetingControllerTest.java | 8 -------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java index 29d06e3..ea49d29 100644 --- a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java +++ b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java @@ -1,8 +1,11 @@ package com.dnd.jjakkak.config; +import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; @@ -28,6 +31,16 @@ @ExtendWith(RestDocumentationExtension.class) public abstract class AbstractRestDocsTest { + /** + * jwtAuthentication Filter 내부에서 사용하는 Bean 등록 + */ + @MockBean + JwtProvider jwtProvider; + + @MockBean + MemberRepository memberRepository; + + @Autowired protected RestDocumentationResultHandler restDocs; diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java index 0eaff7a..d4ea7b9 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java @@ -6,8 +6,6 @@ import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.repository.MemberRepository; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.junit.jupiter.api.DisplayName; @@ -45,12 +43,6 @@ class MeetingControllerTest extends AbstractRestDocsTest { @MockBean MeetingService meetingService; - @MockBean - JwtProvider jwtProvider; - - @MockBean - MemberRepository memberRepository; - @Autowired ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); From ddb80e2a930530de7c15cfac379a7d0f6108a0f0 Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 13:43:33 +0900 Subject: [PATCH 38/49] =?UTF-8?q?test:=20=EB=AA=A8=EC=9E=84=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MeetingControllerTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java index d4ea7b9..bbc9370 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java @@ -5,6 +5,7 @@ import com.dnd.jjakkak.domain.meeting.MeetingDummy; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; +import com.dnd.jjakkak.domain.meeting.exception.MeetingNotFoundException; import com.dnd.jjakkak.domain.meeting.service.MeetingService; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; @@ -154,6 +155,33 @@ void get_byUuid() throws Exception { ))); } + @Test + @DisplayName("모임 조회 테스트 - 실패") + @JjakkakMockUser + void get_byUuid_fail() throws Exception { + + // given + when(meetingService.getMeetingByUuid(anyString())) + .thenThrow(new MeetingNotFoundException()); + + // expected + mockMvc.perform(get("/api/v1/meeting/{meetingUuid}", "ABCD1234") + .contentType(MediaType.APPLICATION_JSON)) + .andExpectAll( + status().isNotFound(), + jsonPath("$.code").value(404), + jsonPath("$.message").value("모임을 찾을 수 없습니다.") + ) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingUuid").description("모임 UUID")), + responseFields( + fieldWithPath("code").description("에러 코드"), + fieldWithPath("message").description("에러 메시지"), + fieldWithPath("validation").description("유효성 검사 오류 목록") + ))); + } + @Test @JjakkakMockUser @DisplayName("모임 확정 일정 수정 테스트") From 6ec02d2a4fa0542125d014bb1fa264ff554efb4a Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 13:43:40 +0900 Subject: [PATCH 39/49] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=8D=94=EB=AF=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/category/CategoryDummy.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java diff --git a/src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java b/src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java new file mode 100644 index 0000000..ae10ab1 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java @@ -0,0 +1,66 @@ +package com.dnd.jjakkak.domain.category; + +import com.dnd.jjakkak.domain.category.dto.response.CategoryResponseDto; + +import java.util.List; + +/** + * 카테고리 더미 데이터 클래스입니다. + * + * @author 정승조 + * @version 2024. 08. 06. + */ +public class CategoryDummy { + + public static List getCategoryResponseDtoList() { + + return List.of( + CategoryResponseDto.builder() + .categoryId(1L) + .categoryName("학교") + .build(), + + CategoryResponseDto.builder() + .categoryId(2L) + .categoryName("친구") + .build(), + + CategoryResponseDto.builder() + .categoryId(3L) + .categoryName("팀플") + .build(), + + CategoryResponseDto.builder() + .categoryId(4L) + .categoryName("회의") + .build(), + + CategoryResponseDto.builder() + .categoryId(5L) + .categoryName("스터디") + .build(), + + CategoryResponseDto.builder() + .categoryId(6L) + .categoryName("취미") + .build(), + + CategoryResponseDto.builder() + .categoryId(7L) + .categoryName("봉사") + .build(), + + CategoryResponseDto.builder() + .categoryId(8L) + .categoryName("기타") + .build() + ); + } + + public static CategoryResponseDto getCategoryResponseDto() { + return CategoryResponseDto.builder() + .categoryId(1L) + .categoryName("학교") + .build(); + } +} From 85f02b723d2c8a5b191135904aea8ce494352912 Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 13:44:00 +0900 Subject: [PATCH 40/49] =?UTF-8?q?test:=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryControllerTest.java | 102 +++++++----------- 1 file changed, 41 insertions(+), 61 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java index 64fe7cd..019456b 100644 --- a/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java @@ -1,28 +1,25 @@ package com.dnd.jjakkak.domain.category.controller; -import com.dnd.jjakkak.domain.category.entity.Category; -import com.dnd.jjakkak.domain.category.repository.CategoryRepository; +import com.dnd.jjakkak.config.AbstractRestDocsTest; +import com.dnd.jjakkak.config.JjakkakMockUser; +import com.dnd.jjakkak.domain.category.CategoryDummy; +import com.dnd.jjakkak.domain.category.exception.CategoryNotFoundException; import com.dnd.jjakkak.domain.category.service.CategoryService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; -import java.util.List; - -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * 카테고리 컨트롤러 테스트 클래스입니다. @@ -30,80 +27,60 @@ * @author 정승조 * @version 2024. 07. 24. */ -@ActiveProfiles("test") -@SpringBootTest +@WebMvcTest(CategoryController.class) @AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) -@AutoConfigureMockMvc -class CategoryControllerTest { - - @Autowired - MockMvc mockMvc; +class CategoryControllerTest extends AbstractRestDocsTest { - @Autowired + @MockBean CategoryService categoryService; - @Autowired - CategoryRepository categoryRepository; - @Test @DisplayName("카테고리 전체 목록 조회") - void testGetCategoryList() throws Exception { + @JjakkakMockUser + void get_list() throws Exception { // given - Category school = Category.builder() - .categoryName("학교") - .build(); - - Category friend = Category.builder() - .categoryName("친구") - .build(); - - Category meeting = Category.builder() - .categoryName("회의") - .build(); - - categoryRepository.saveAll(List.of(school, friend, meeting)); + when(categoryService.getCategoryList()).thenReturn(CategoryDummy.getCategoryResponseDtoList()); // expected mockMvc.perform(get("/api/v1/categories")) .andExpectAll( status().isOk(), - content().contentType(MediaType.APPLICATION_JSON), jsonPath("$[?(@.categoryName == '학교')]").exists(), jsonPath("$[?(@.categoryName == '친구')]").exists(), - jsonPath("$[?(@.categoryName == '회의')]").exists() + jsonPath("$[?(@.categoryName == '팀플')]").exists(), + jsonPath("$[?(@.categoryName == '회의')]").exists(), + jsonPath("$[?(@.categoryName == '스터디')]").exists(), + jsonPath("$[?(@.categoryName == '취미')]").exists(), + jsonPath("$[?(@.categoryName == '봉사')]").exists(), + jsonPath("$[?(@.categoryName == '기타')]").exists() ) - .andDo(document("category/getCategoryList/success", - preprocessResponse(prettyPrint()), + .andDo(restDocs.document( responseFields( fieldWithPath("[].categoryId").description("카테고리 아이디"), fieldWithPath("[].categoryName").description("카테고리 이름") - ) - )); + )) + ); } @Test @DisplayName("카테고리 단건 조회 - 성공") - void testGetCategory_Success() throws Exception { + @JjakkakMockUser + void get_success() throws Exception { - // given - Category school = Category.builder() - .categoryName("기타") - .build(); + // given (1L, "학교") + when(categoryService.getCategory(anyLong())).thenReturn(CategoryDummy.getCategoryResponseDto()); - categoryRepository.save(school); // expected - mockMvc.perform(get("/api/v1/categories/{id}", school.getCategoryId())) + mockMvc.perform(get("/api/v1/categories/{id}", 1L)) .andExpectAll( status().isOk(), - jsonPath("$.categoryId").value(school.getCategoryId()), - jsonPath("$.categoryName").value("기타") + jsonPath("$.categoryId").value(1), + jsonPath("$.categoryName").value("학교") ) - .andDo(document("category/getCategory/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), + .andDo(restDocs.document( pathParameters( parameterWithName("id").description("카테고리 아이디")), responseFields( @@ -115,14 +92,17 @@ void testGetCategory_Success() throws Exception { @Test @DisplayName("카테고리 단건 조회 - 실패 (404)") - void testGetCategory_Fail() throws Exception { + @JjakkakMockUser + void get_fail() throws Exception { + + // given + when(categoryService.getCategory(anyLong())) + .thenThrow(new CategoryNotFoundException()); // expected - mockMvc.perform(get("/api/v1/categories/{id}", Long.MAX_VALUE)) + mockMvc.perform(get("/api/v1/categories/{id}", 100L)) .andExpect(status().isNotFound()) - .andDo(document("category/getCategory/fail-404", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), + .andDo(restDocs.document( pathParameters( parameterWithName("id").description("카테고리 아이디")), responseFields( From ef4879c8d4cfdfe25613d153bf5ae9a21c702ff6 Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 13:58:01 +0900 Subject: [PATCH 41/49] =?UTF-8?q?test:=20category=20asciidoc=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/api/category.adoc | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/docs/asciidoc/api/category.adoc b/src/docs/asciidoc/api/category.adoc index 6756319..0662993 100644 --- a/src/docs/asciidoc/api/category.adoc +++ b/src/docs/asciidoc/api/category.adoc @@ -3,33 +3,30 @@ === 카테고리 목록 조회 .HTTP Request -include::{snippets}/category/getCategoryList/success/http-request.adoc[] +include::{snippets}/category-controller-test/get_list/http-request.adoc[] .HTTP Response -include::{snippets}/category/getCategoryList/success/http-response.adoc[] +include::{snippets}/category-controller-test/get_list/http-response.adoc[] .Response Fields -include::{snippets}/category/getCategoryList/success/response-fields.adoc[] +include::{snippets}/category-controller-test/get_list/response-fields.adoc[] === 카테고리 개별 조회 - 성공 -include::{snippets}/category/getCategory/success/path-parameters.adoc[] +include::{snippets}/category-controller-test/get_success/path-parameters.adoc[] .HTTP Request -include::{snippets}/category/getCategory/success/http-request.adoc[] +include::{snippets}/category-controller-test/get_success/http-request.adoc[] .HTTP Response -include::{snippets}/category/getCategory/success/http-response.adoc[] -include::{snippets}/category/getCategory/success/response-fields.adoc[] +include::{snippets}/category-controller-test/get_success/http-response.adoc[] === 카테고리 개별 조회 - 실패 .HTTP Request -include::{snippets}/category/getCategory/fail-404/http-request.adoc[] +include::{snippets}/category-controller-test/get_fail/http-request.adoc[] .HTTP Response -include::{snippets}/category/getCategory/fail-404/http-response.adoc[] +include::{snippets}/category-controller-test/get_fail/http-response.adoc[] -.Error Response -include::{snippets}/category/getCategory/fail-404/response-fields.adoc[] From 754e437fb81a4bc2020720230c1dd8cfc92492fd Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 6 Aug 2024 16:33:23 +0900 Subject: [PATCH 42/49] =?UTF-8?q?test:=20AuthController=20Test=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/AuthControllerTest.java | 162 +++++++----------- 1 file changed, 60 insertions(+), 102 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java index 0f14614..9ab568f 100644 --- a/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java @@ -1,141 +1,99 @@ package com.dnd.jjakkak.domain.member.controller; -import com.dnd.jjakkak.domain.member.entity.RefreshToken; -import com.dnd.jjakkak.domain.member.repository.BlacklistedTokenRepository; -import com.dnd.jjakkak.domain.member.repository.RefreshTokenRepository; -import com.dnd.jjakkak.domain.member.service.BlacklistService; -import com.dnd.jjakkak.domain.member.service.RefreshTokenService; -import jakarta.persistence.EntityManager; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Disabled; +import com.dnd.jjakkak.domain.member.jwt.filter.JwtAuthenticationFilter; +import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * AuthController 테스트 클래스입니다. * * @author 류태웅 - * @version 2024. 07. 27. + * @version 2024. 08. 06 */ @ActiveProfiles("test") -@SpringBootTest +@WebMvcTest(AuthController.class) @AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) @AutoConfigureMockMvc class AuthControllerTest { @Autowired - MockMvc mockMvc; + private MockMvc mockMvc; - @Autowired - RefreshTokenService refreshTokenService; - - @Autowired - BlacklistService blacklistService; + @MockBean + private JwtProvider jwtProvider; - @Autowired - EntityManager entityManager; + @MockBean + private JwtAuthenticationFilter jwtAuthenticationFilter; - @Autowired - RefreshTokenRepository refreshTokenRepository; + @MockBean + private MemberRepository memberRepository; - @Autowired - BlacklistedTokenRepository blacklistedTokenRepository; + @BeforeEach + public void setUp(WebApplicationContext webApplicationContext) { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .apply(springSecurity()) + .defaultRequest(get("/**").with(csrf())) + .build(); - @AfterEach - void clear() { - refreshTokenRepository.deleteAll(); - blacklistedTokenRepository.deleteAll(); } @Test - @Disabled - @DisplayName("로그아웃 테스트 - 성공") - void testLogoutSuccess() throws Exception { - - // given - RefreshToken token = new RefreshToken("valid_refresh_token", 1L); - refreshTokenRepository.save(token); - - String refreshToken = "Bearer valid_refresh_token"; - - // expected - mockMvc.perform(post("/api/v1/logout") - .header("Authorization", refreshToken)) - .andExpectAll( - status().isOk(), - content().string("Logout successful") - ) - .andDo(document("auth/logout/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - responseFields( - fieldWithPath("status").description("상태 코드"), - fieldWithPath("message").description("응답 메시지") - ) - )); + @DisplayName("로그인 상태 확인 - 확인됨") + //@JjakkakMockUser + public void testCheckAuthAuthenticated() throws Exception { + // JwtProvider의 validate 메소드가 "user"를 반환하도록 설정 + when(jwtProvider.validate(anyString())).thenReturn("user"); + + mockMvc.perform(get("/check-auth") + .header("Authorization", "Bearer valid-token") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isAuthenticated").value(true)); } @Test - @Disabled - @DisplayName("로그아웃 테스트 - 실패 (유효하지 않은 토큰)") - void testLogoutFailInvalidToken() throws Exception { - - // given - String refreshToken = "Bearer invalid_refresh_token"; - - // expected - mockMvc.perform(post("/api/v1/logout") - .header("Authorization", refreshToken)) - .andExpectAll( - status().isUnauthorized(), - jsonPath("$.code").value(401), - jsonPath("$.message").value("Invalid Refresh Token") - ) - .andDo(document("auth/logout/fail-invalid-token", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - responseFields( - fieldWithPath("code").description("상태 코드"), - fieldWithPath("message").description("응답 메시지"), - fieldWithPath("validation").description("유효성 검사") - ) - )); + @DisplayName("로그인 상태 확인 - 토큰이 인증되질 않음") + //@JjakkakMockUser + public void testCheckAuthNotAuthenticated() throws Exception { + // JwtProvider의 validate 메소드가 null을 반환하도록 설정 + when(jwtProvider.validate(anyString())).thenReturn(null); + + mockMvc.perform(get("/check-auth") + .header("Authorization", "Bearer invalid-token") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isAuthenticated").value(false)); } @Test - @DisplayName("로그아웃 테스트 - 실패 (헤더 없음)") - void testLogoutFailNoHeader() throws Exception { - - // expected - mockMvc.perform(post("/api/v1/logout") - .contentType(MediaType.APPLICATION_JSON)) - .andExpectAll( - status().isBadRequest(), - jsonPath("$.code").value(400), - jsonPath("$.message").value("Invalid Header Error") - ) - .andDo(document("auth/logout/fail-no-header", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), - responseFields( - fieldWithPath("code").description("상태 코드"), - fieldWithPath("message").description("응답 메시지"), - fieldWithPath("validation").description("유효성 검사") - ) - )); + @DisplayName("로그인 상태 확인 - 토큰 없음") + //@JjakkakMockUser + public void testCheckAuthNoToken() throws Exception { + mockMvc.perform(get("/check-auth") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.isAuthenticated").value(false)); } } From 888c73c46aa4aa54436aae6fe16d5a3ceea4a753 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 6 Aug 2024 17:13:11 +0900 Subject: [PATCH 43/49] =?UTF-8?q?test:=20MemberController=20Test=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MemberControllerTest.java | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java diff --git a/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java new file mode 100644 index 0000000..1cedf7a --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java @@ -0,0 +1,147 @@ +package com.dnd.jjakkak.domain.member.controller; + +import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; +import com.dnd.jjakkak.domain.meeting.entity.Meeting; +import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateNicknameRequestDto; +import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateProfileRequestDto; +import com.dnd.jjakkak.domain.member.jwt.filter.JwtAuthenticationFilter; +import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.service.MemberService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * MemberController 테스트 클래스입니다. + * + * @author 류태웅 + * @version 2024. 08. 06 + */ + +@ActiveProfiles("test") +@WebMvcTest(MemberController.class) +@AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) +@AutoConfigureMockMvc +public class MemberControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private MemberService memberService; + + @MockBean + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @MockBean + private JwtProvider jwtProvider; + + @BeforeEach + public void setUp(WebApplicationContext webApplicationContext) { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(webApplicationContext) + .apply(springSecurity()) + .defaultRequest(get("/**").with(csrf())) + .build(); + } + + @Test + @WithMockUser + @DisplayName("회원 모임 리스트 조회") + public void testGetMemberListByMemberId() throws Exception { + Meeting meeting = Meeting.builder() + .meetingName("Test Meeting") + .meetingStartDate(LocalDate.now()) + .meetingEndDate(LocalDate.now().plusDays(1)) + .numberOfPeople(10) + .isAnonymous(false) + .voteEndDate(LocalDateTime.now().plusDays(1)) + .meetingLeaderId(1L) + .meetingUuid("uuid") + .build(); + + MeetingResponseDto meetingResponseDto = MeetingResponseDto.builder() + .meeting(meeting) + .build(); + + when(memberService.getMeetingListByMemberId(anyLong())).thenReturn(Collections.singletonList(meetingResponseDto)); + + mockMvc.perform(get("/api/v1/member/{memberId}/meetingList", 1L) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].meetingName").value("Test Meeting")) + .andExpect(jsonPath("$[0].meetingStartDate").exists()) + .andExpect(jsonPath("$[0].meetingEndDate").exists()) + .andExpect(jsonPath("$[0].numberOfPeople").value(10)) + .andExpect(jsonPath("$[0].isAnonymous").value(false)) + .andExpect(jsonPath("$[0].voteEndDate").exists()) + .andExpect(jsonPath("$[0].meetingLeaderId").value(1L)) + .andExpect(jsonPath("$[0].meetingUuid").value("uuid")) + .andDo(print()); + } + + @Test + @WithMockUser + @DisplayName("회원 닉네임 업데이트") + public void testUpdateNickname() throws Exception { + doNothing().when(memberService).updateNickname(anyLong(), any(MemberUpdateNicknameRequestDto.class)); + + mockMvc.perform(patch("/api/v1/member/{memberId}/nickname", 9L) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"memberNickname\": \"newName\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()); + } + + @Test + @WithMockUser + @DisplayName("회원 프로필 업데이트") + public void testUpdateProfile() throws Exception { + doNothing().when(memberService).updateProfile(anyLong(), any(MemberUpdateProfileRequestDto.class)); + + mockMvc.perform(patch("/api/v1/member/{memberId}/profile", 9L) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"memberProfile\": \"http://newProfile\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()); + } + + @Test + @WithMockUser + @DisplayName("회원 삭제") + public void testDeleteMember() throws Exception { + doNothing().when(memberService).deleteMember(anyLong()); + + mockMvc.perform(delete("/api/v1/member/{memberId}", 9L) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(print()); + } +} From d457ad4499f8bcd8bd2f98a5ae2ab43dca159d45 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 6 Aug 2024 17:25:47 +0900 Subject: [PATCH 44/49] =?UTF-8?q?fix:=20=EB=B3=91=ED=95=A9=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/filter/JwtAuthenticationFilter.java | 8 ++-- .../meeting/controller/MeetingController.java | 30 ++++++-------- .../meeting/service/MeetingService.java | 39 ++++-------------- .../meeting/service/MeetingServiceTest.java | 41 ++----------------- .../controller/MemberControllerTest.java | 4 +- 5 files changed, 31 insertions(+), 91 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java index c451c07..35e7783 100644 --- a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java @@ -1,11 +1,11 @@ package com.dnd.jjakkak.domain.jwt.filter; -import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.entity.Role; -import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; import com.dnd.jjakkak.domain.jwt.exception.AccessTokenExpiredException; import com.dnd.jjakkak.domain.jwt.exception.MalformedTokenException; import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.entity.Member; +import com.dnd.jjakkak.domain.member.entity.Role; +import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import com.dnd.jjakkak.global.config.security.SecurityConfig; import io.jsonwebtoken.ExpiredJwtException; @@ -83,7 +83,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse authorities.add(new SimpleGrantedAuthority(role.toString())); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(kakaoId, null, authorities); + AbstractAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(member, null, authorities); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); securityContext.setAuthentication(authenticationToken); diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java index 291bdc9..3ed54bf 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/controller/MeetingController.java @@ -6,14 +6,15 @@ import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.service.MeetingService; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; +import com.dnd.jjakkak.domain.member.entity.Member; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Objects; /** * 모임 컨트롤러 클래스입니다. @@ -31,21 +32,19 @@ public class MeetingController { /** * 모임을 생성하는 메서드입니다. * - * @param accessToken JWT Token (Access Token) - * @param requestDto 모임 생성 요청 DTO + * @param member 로그인한 회원 정보 + * @param requestDto 모임 생성 요청 DTO * @return 201 (CREATED), body: 모임 생성 응답 DTO (UUID) */ @PostMapping - public ResponseEntity createGroup(@RequestHeader("Authorization") String accessToken, + public ResponseEntity createGroup(@AuthenticationPrincipal Member member, @Valid @RequestBody MeetingCreateRequestDto requestDto) { - if (Objects.isNull(accessToken)) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } + MeetingCreateResponseDto response = meetingService.createMeeting(member.getMemberId(), requestDto); return ResponseEntity .status(HttpStatus.CREATED) - .body(meetingService.createMeeting(accessToken, requestDto)); + .body(response); } /** @@ -84,22 +83,19 @@ public ResponseEntity confirmMeeting(@PathVariable("meetingId") Long id, return ResponseEntity.ok().build(); } + /** * 모임을 삭제하는 메서드입니다. * - * @param accessToken JWT Token (Access Token) - * @param id 삭제할 모임 ID + * @param member 로그인한 회원 정보 + * @param id 삭제할 모임 ID * @return 200 (OK) */ @DeleteMapping("/{meetingId}") - public ResponseEntity deleteMeeting(@RequestHeader("Authorization") String accessToken, + public ResponseEntity deleteMeeting(@AuthenticationPrincipal Member member, @PathVariable("meetingId") Long id) { - if (Objects.isNull(accessToken)) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - - meetingService.deleteMeeting(accessToken, id); + meetingService.deleteMeeting(member.getMemberId(), id); return ResponseEntity.ok().build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index 4149405..a81be91 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -16,15 +16,11 @@ import com.dnd.jjakkak.domain.meetingmember.repository.MeetingMemberRepository; import com.dnd.jjakkak.domain.member.dto.response.MemberResponseDto; import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; -import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Objects; import java.util.UUID; /** @@ -41,23 +37,20 @@ public class MeetingService { private final MeetingCategoryRepository meetingCategoryRepository; private final CategoryRepository categoryRepository; private final MeetingMemberRepository meetingMemberRepository; - private final JwtProvider jwtProvider; - private final MemberRepository memberRepository; /** * 모임을 생성하는 메서드입니다. * - * @param token JWT Token + * @param memberId 모임을 생성하는 회원 ID (리더 ID) * @param requestDto 모임 생성 요청 DTO * @return 모임 생성 응답 DTO (UUID) */ @Transactional - public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequestDto requestDto) { + public MeetingCreateResponseDto createMeeting(Long memberId, MeetingCreateRequestDto requestDto) { // checkMeetingDate 메서드를 호출하여 유효성 검사를 진행합니다. requestDto.checkMeetingDate(); - Member member = getMemberByToken(token); String uuid = generateUuid(); // 모임 생성 로직 @@ -68,7 +61,7 @@ public MeetingCreateResponseDto createMeeting(String token, MeetingCreateRequest .numberOfPeople(requestDto.getNumberOfPeople()) .isAnonymous(requestDto.getIsAnonymous()) .voteEndDate(requestDto.getVoteEndDate()) - .meetingLeaderId(member.getMemberId()) + .meetingLeaderId(memberId) .meetingUuid(uuid) .build(); @@ -127,18 +120,18 @@ public void confirmMeeting(Long id, MeetingConfirmRequestDto requestDto) { /** * 모임을 삭제하는 메서드입니다. * - * @param token JWT Token - * @param id 모임 ID + * @param memberId 회원 ID + * @param id 모임 ID */ @Transactional - public void deleteMeeting(String token, Long id) { + public void deleteMeeting(Long memberId, Long id) { - Member member = getMemberByToken(token); Meeting meeting = meetingRepository.findById(id) .orElseThrow(MeetingNotFoundException::new); - if (!meeting.getMeetingLeaderId().equals(member.getMemberId())) { + // 요청한 회원이 모임의 리더가 아닌 경우 예외 처리 + if (!meeting.getMeetingLeaderId().equals(memberId)) { throw new MeetingUnauthorizedException(); } @@ -177,18 +170,4 @@ private String generateUuid() { return uuid; } - - /** - * 토큰을 통해 회원 정보를 조회하는 메서드입니다. - * - * @param token AccessToken - * @return Member Entity - */ - private Member getMemberByToken(String token) { - String kakaoId = Objects.requireNonNull(jwtProvider.validate(token)); - - return memberRepository.findByKakaoId(Long.parseLong(kakaoId)) - .orElseThrow(MemberNotFoundException::new); - } - -} +} \ No newline at end of file diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java index 0d2fe4a..73bfda4 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/service/MeetingServiceTest.java @@ -9,16 +9,12 @@ import com.dnd.jjakkak.domain.meeting.exception.MeetingNotFoundException; import com.dnd.jjakkak.domain.meeting.repository.MeetingRepository; import com.dnd.jjakkak.domain.meetingcategory.repository.MeetingCategoryRepository; -import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.repository.MemberRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; import java.time.LocalDate; import java.time.LocalDateTime; @@ -49,12 +45,6 @@ class MeetingServiceTest { @Mock MeetingCategoryRepository meetingCategoryRepository; - @Mock - JwtProvider jwtProvider; - - @Mock - MemberRepository memberRepository; - @Test @DisplayName("모임 생성 테스트 - 성공") void testCreateMeeting() { @@ -69,18 +59,11 @@ void testCreateMeeting() { .categoryName("회의") .build(); - Member member = Member.builder() - .kakaoId(1L) - .memberNickname("seungjo") - .build(); - when(categoryRepository.findById(1L)).thenReturn(Optional.of(teamProject)); when(categoryRepository.findById(2L)).thenReturn(Optional.of(meeting)); - when(jwtProvider.validate(anyString())).thenReturn("1"); - when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); // when - meetingService.createMeeting("access_token", actual); + meetingService.createMeeting(1L, actual); // then verify(meetingRepository, times(1)).save(any()); @@ -149,19 +132,10 @@ void testDeleteMeeting_Success() { .meetingLeaderId(1L) .build(); - Member member = Member.builder() - .kakaoId(1L) - .memberNickname("seungjo") - .build(); - - ReflectionTestUtils.setField(member, "memberId", 1L); - - when(jwtProvider.validate(anyString())).thenReturn("1"); - when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); when(meetingRepository.findById(anyLong())).thenReturn(Optional.of(meeting)); // when - meetingService.deleteMeeting("access_token", 1L); + meetingService.deleteMeeting(1L, 1L); // then verify(meetingRepository, times(1)).deleteById(1L); @@ -171,18 +145,9 @@ void testDeleteMeeting_Success() { @Test @DisplayName("모임 삭제 테스트 - 실패 (존재하지 않는 모임)") void testDeleteMeeting_Fail() { - // given - Member member = Member.builder() - .kakaoId(1L) - .memberNickname("seungjo") - .build(); - - - when(jwtProvider.validate(anyString())).thenReturn("1"); - when(memberRepository.findByKakaoId(anyLong())).thenReturn(Optional.of(member)); // expected assertThrows(MeetingNotFoundException.class, - () -> meetingService.deleteMeeting("invalid_token", 1L)); + () -> meetingService.deleteMeeting(1L, 1L)); } } \ No newline at end of file diff --git a/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java index 1cedf7a..28d0f15 100644 --- a/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java @@ -1,11 +1,11 @@ package com.dnd.jjakkak.domain.member.controller; +import com.dnd.jjakkak.domain.jwt.filter.JwtAuthenticationFilter; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.entity.Meeting; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateNicknameRequestDto; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateProfileRequestDto; -import com.dnd.jjakkak.domain.member.jwt.filter.JwtAuthenticationFilter; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.service.MemberService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; From cd52706e486e2accb5bca3307584e6bec9d27864 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 6 Aug 2024 17:41:43 +0900 Subject: [PATCH 45/49] =?UTF-8?q?fix:=20=EB=B3=91=ED=95=A9=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 + src/docs/asciidoc/api/category.adoc | 19 +- src/docs/asciidoc/api/meeting.adoc | 81 ++----- .../jwt/filter/JwtAuthenticationFilter.java | 1 + .../meeting/service/MeetingService.java | 2 +- .../jjakkak/config/AbstractRestDocsTest.java | 63 +++++ .../config/JjakkakMockSecurityContext.java | 44 ++++ .../dnd/jjakkak/config/JjakkakMockUser.java | 21 ++ .../jjakkak/config/RestDocsConfiguration.java | 26 ++ .../domain/category/CategoryDummy.java | 66 ++++++ .../controller/CategoryControllerTest.java | 102 ++++---- .../jjakkak/domain/meeting/MeetingDummy.java | 31 +-- .../controller/MeetingControllerTest.java | 224 ++++++++++++++++++ 13 files changed, 520 insertions(+), 164 deletions(-) create mode 100644 src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java create mode 100644 src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java create mode 100644 src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java create mode 100644 src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java create mode 100644 src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java create mode 100644 src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java diff --git a/build.gradle b/build.gradle index 930cfce..5aef1cc 100644 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,10 @@ dependencies { // Spring REST Docs asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + + // test lombok + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' } tasks.named('test') { diff --git a/src/docs/asciidoc/api/category.adoc b/src/docs/asciidoc/api/category.adoc index 6756319..0662993 100644 --- a/src/docs/asciidoc/api/category.adoc +++ b/src/docs/asciidoc/api/category.adoc @@ -3,33 +3,30 @@ === 카테고리 목록 조회 .HTTP Request -include::{snippets}/category/getCategoryList/success/http-request.adoc[] +include::{snippets}/category-controller-test/get_list/http-request.adoc[] .HTTP Response -include::{snippets}/category/getCategoryList/success/http-response.adoc[] +include::{snippets}/category-controller-test/get_list/http-response.adoc[] .Response Fields -include::{snippets}/category/getCategoryList/success/response-fields.adoc[] +include::{snippets}/category-controller-test/get_list/response-fields.adoc[] === 카테고리 개별 조회 - 성공 -include::{snippets}/category/getCategory/success/path-parameters.adoc[] +include::{snippets}/category-controller-test/get_success/path-parameters.adoc[] .HTTP Request -include::{snippets}/category/getCategory/success/http-request.adoc[] +include::{snippets}/category-controller-test/get_success/http-request.adoc[] .HTTP Response -include::{snippets}/category/getCategory/success/http-response.adoc[] -include::{snippets}/category/getCategory/success/response-fields.adoc[] +include::{snippets}/category-controller-test/get_success/http-response.adoc[] === 카테고리 개별 조회 - 실패 .HTTP Request -include::{snippets}/category/getCategory/fail-404/http-request.adoc[] +include::{snippets}/category-controller-test/get_fail/http-request.adoc[] .HTTP Response -include::{snippets}/category/getCategory/fail-404/http-response.adoc[] +include::{snippets}/category-controller-test/get_fail/http-response.adoc[] -.Error Response -include::{snippets}/category/getCategory/fail-404/response-fields.adoc[] diff --git a/src/docs/asciidoc/api/meeting.adoc b/src/docs/asciidoc/api/meeting.adoc index c153d80..29e2372 100644 --- a/src/docs/asciidoc/api/meeting.adoc +++ b/src/docs/asciidoc/api/meeting.adoc @@ -3,95 +3,52 @@ === 모임 생성 - 성공 .HTTP Request -include::{snippets}/meeting/create/success/http-request.adoc[] +include::{snippets}/meeting-controller-test/create_success/http-request.adoc[] .Request Fields -include::{snippets}/meeting/create/success/request-fields.adoc[] +include::{snippets}/meeting-controller-test/create_success/request-fields.adoc[] .HTTP Response -include::{snippets}/meeting/create/success/http-response.adoc[] +include::{snippets}/meeting-controller-test/create_success/http-response.adoc[] === 모임 생성 - 실패 .HTTP Request -include::{snippets}/meeting/create/fail/http-request.adoc[] +include::{snippets}/meeting-controller-test/create_fail_invalid/http-request.adoc[] .HTTP Response -include::{snippets}/meeting/create/fail/http-response.adoc[] +include::{snippets}/meeting-controller-test/create_fail_invalid/http-response.adoc[] .Error Response -include::{snippets}/meeting/create/fail/response-fields.adoc[] +include::{snippets}/meeting-controller-test/create_fail_invalid/response-fields.adoc[] -=== 모임 전체 조회 +=== 모임 조회 (UUID) -.Response Fields -include::{snippets}/meeting/getList/success/response-fields.adoc[] +include::{snippets}/meeting-controller-test/get_by-uuid/path-parameters.adoc[] .HTTP Request -include::{snippets}/meeting/getList/success/http-request.adoc[] +include::{snippets}/meeting-controller-test/get_by-uuid/http-request.adoc[] .HTTP Response -include::{snippets}/meeting/getList/success/http-response.adoc[] +include::{snippets}/meeting-controller-test/get_by-uuid/http-response.adoc[] -=== 모임 개별 조회 - 성공 +include::{snippets}/meeting-controller-test/get_by-uuid/response-fields.adoc[] -include::{snippets}/meeting/get/success/path-parameters.adoc[] +=== 모임 확정 일정 수정 .HTTP Request -include::{snippets}/meeting/get/success/http-request.adoc[] +include::{snippets}/meeting-controller-test/update_confirmed-schedule/http-request.adoc[] +include::{snippets}/meeting-controller-test/update_confirmed-schedule/request-fields.adoc[] .HTTP Response -include::{snippets}/meeting/get/success/http-response.adoc[] +include::{snippets}/meeting-controller-test/update_confirmed-schedule/http-response.adoc[] -=== 모임 개별 조회 - 실패 - -include::{snippets}/meeting/get/fail/path-parameters.adoc[] - -.HTTP Request -include::{snippets}/meeting/get/fail/http-request.adoc[] - -include::{snippets}/meeting/get/fail/http-response.adoc[] - -=== 모임 수정 - 성공 - -include::{snippets}/meeting/update/success/path-parameters.adoc[] - -.HTTP Request -include::{snippets}/meeting/update/success/http-request.adoc[] - - -.Request Fields -include::{snippets}/meeting/update/success/request-fields.adoc[] - - - -.HTTP Response -include::{snippets}/meeting/update/success/http-response.adoc[] - -=== 모임 수정 - 실패 - -.HTTP Request -include::{snippets}/meeting/update/fail/http-request.adoc[] - -.HTTP Response -include::{snippets}/meeting/update/fail/http-response.adoc[] - -=== 모임 삭제 - 성공 - -include::{snippets}/meeting/delete/success/path-parameters.adoc[] -.HTTP Request -include::{snippets}/meeting/delete/success/http-request.adoc[] - -.HTTP Response -include::{snippets}/meeting/delete/success/http-response.adoc[] - -=== 모임 삭제 - 실패 - -include::{snippets}/meeting/delete/fail/path-parameters.adoc[] +=== 모임 삭제 .HTTP Request -include::{snippets}/meeting/delete/fail/http-request.adoc[] +include::{snippets}/meeting-controller-test/delete_success/path-parameters.adoc[] +include::{snippets}/meeting-controller-test/delete_success/http-request.adoc[] .HTTP Response -include::{snippets}/meeting/delete/fail/http-response.adoc[] +include::{snippets}/meeting-controller-test/delete_success/http-response.adoc[] diff --git a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java index 35e7783..c27dd4c 100644 --- a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java @@ -34,6 +34,7 @@ /** * 검증을 실행하는 필터입니다. + * * @author 류태웅 * @version 2024. 08. 03. */ diff --git a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java index a81be91..608da8a 100644 --- a/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java +++ b/src/main/java/com/dnd/jjakkak/domain/meeting/service/MeetingService.java @@ -170,4 +170,4 @@ private String generateUuid() { return uuid; } -} \ No newline at end of file +} diff --git a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java new file mode 100644 index 0000000..3278e21 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java @@ -0,0 +1,63 @@ +package com.dnd.jjakkak.config; + +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; + +/** + * REST Docs 설정을 적용한 테스트 클래스입니다. (REST Docs 최상위 클래스) + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@Import(RestDocsConfiguration.class) +@ExtendWith(RestDocumentationExtension.class) +public abstract class AbstractRestDocsTest { + + @Autowired + protected RestDocumentationResultHandler restDocs; + @Autowired + protected MockMvc mockMvc; + /** + * jwtAuthentication Filter 내부에서 사용하는 Bean 등록 + */ + @MockBean + JwtProvider jwtProvider; + @MockBean + MemberRepository memberRepository; + + @BeforeEach + void setUp(WebApplicationContext context, RestDocumentationContextProvider restDocumentation) { + + + this.mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(documentationConfiguration(restDocumentation)) + .alwaysDo(MockMvcResultHandlers.print()) + .alwaysDo(restDocs) + .addFilters(new CharacterEncodingFilter("UTF-8", true)) + .apply(springSecurity()) + .defaultRequest(post("/**").with(csrf())) + .defaultRequest(get("/**").with(csrf())) + .defaultRequest(patch("/**").with(csrf())) + .defaultRequest(delete("/**").with(csrf())) + .build(); + } +} diff --git a/src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java b/src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java new file mode 100644 index 0000000..0b95241 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/JjakkakMockSecurityContext.java @@ -0,0 +1,44 @@ +package com.dnd.jjakkak.config; + +import com.dnd.jjakkak.domain.member.entity.Member; +import com.dnd.jjakkak.domain.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +import java.util.List; + +/** + * 테스트에서 사용할 MockUser 어노테이션에 SecurityContext 값을 설정하는 클래스입니다. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@RequiredArgsConstructor +public class JjakkakMockSecurityContext implements WithSecurityContextFactory { + + private final MemberRepository memberRepository; + + + @Override + public SecurityContext createSecurityContext(JjakkakMockUser annotation) { + + Member member = Member.builder() + .memberNickname(annotation.nickname()) + .kakaoId(annotation.kakaoId()) + .build(); + + memberRepository.save(member); + + SimpleGrantedAuthority role = new SimpleGrantedAuthority("ROLE_USER"); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(member, null, List.of(role)); + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authToken); + + return context; + } +} diff --git a/src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java b/src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java new file mode 100644 index 0000000..928322d --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/JjakkakMockUser.java @@ -0,0 +1,21 @@ +package com.dnd.jjakkak.config; + +import org.springframework.security.test.context.support.WithSecurityContext; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * MockUser 어노테이션입니다. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = JjakkakMockSecurityContext.class) +public @interface JjakkakMockUser { + + String nickname() default "seungjo"; + + long kakaoId() default 1234567890; +} diff --git a/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java b/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java new file mode 100644 index 0000000..0583ecd --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/config/RestDocsConfiguration.java @@ -0,0 +1,26 @@ +package com.dnd.jjakkak.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; +import org.springframework.restdocs.operation.preprocess.Preprocessors; + +/** + * REST Docs 설정 클래스입니다. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@TestConfiguration +public class RestDocsConfiguration { + + @Bean + public RestDocumentationResultHandler write() { + return MockMvcRestDocumentation.document( + "{class-name}/{method-name}", + Preprocessors.preprocessRequest(Preprocessors.prettyPrint()), + Preprocessors.preprocessResponse(Preprocessors.prettyPrint()) + ); + } +} diff --git a/src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java b/src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java new file mode 100644 index 0000000..ae10ab1 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/domain/category/CategoryDummy.java @@ -0,0 +1,66 @@ +package com.dnd.jjakkak.domain.category; + +import com.dnd.jjakkak.domain.category.dto.response.CategoryResponseDto; + +import java.util.List; + +/** + * 카테고리 더미 데이터 클래스입니다. + * + * @author 정승조 + * @version 2024. 08. 06. + */ +public class CategoryDummy { + + public static List getCategoryResponseDtoList() { + + return List.of( + CategoryResponseDto.builder() + .categoryId(1L) + .categoryName("학교") + .build(), + + CategoryResponseDto.builder() + .categoryId(2L) + .categoryName("친구") + .build(), + + CategoryResponseDto.builder() + .categoryId(3L) + .categoryName("팀플") + .build(), + + CategoryResponseDto.builder() + .categoryId(4L) + .categoryName("회의") + .build(), + + CategoryResponseDto.builder() + .categoryId(5L) + .categoryName("스터디") + .build(), + + CategoryResponseDto.builder() + .categoryId(6L) + .categoryName("취미") + .build(), + + CategoryResponseDto.builder() + .categoryId(7L) + .categoryName("봉사") + .build(), + + CategoryResponseDto.builder() + .categoryId(8L) + .categoryName("기타") + .build() + ); + } + + public static CategoryResponseDto getCategoryResponseDto() { + return CategoryResponseDto.builder() + .categoryId(1L) + .categoryName("학교") + .build(); + } +} diff --git a/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java index 64fe7cd..019456b 100644 --- a/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/category/controller/CategoryControllerTest.java @@ -1,28 +1,25 @@ package com.dnd.jjakkak.domain.category.controller; -import com.dnd.jjakkak.domain.category.entity.Category; -import com.dnd.jjakkak.domain.category.repository.CategoryRepository; +import com.dnd.jjakkak.config.AbstractRestDocsTest; +import com.dnd.jjakkak.config.JjakkakMockUser; +import com.dnd.jjakkak.domain.category.CategoryDummy; +import com.dnd.jjakkak.domain.category.exception.CategoryNotFoundException; import com.dnd.jjakkak.domain.category.service.CategoryService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; -import java.util.List; - -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * 카테고리 컨트롤러 테스트 클래스입니다. @@ -30,80 +27,60 @@ * @author 정승조 * @version 2024. 07. 24. */ -@ActiveProfiles("test") -@SpringBootTest +@WebMvcTest(CategoryController.class) @AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) -@AutoConfigureMockMvc -class CategoryControllerTest { - - @Autowired - MockMvc mockMvc; +class CategoryControllerTest extends AbstractRestDocsTest { - @Autowired + @MockBean CategoryService categoryService; - @Autowired - CategoryRepository categoryRepository; - @Test @DisplayName("카테고리 전체 목록 조회") - void testGetCategoryList() throws Exception { + @JjakkakMockUser + void get_list() throws Exception { // given - Category school = Category.builder() - .categoryName("학교") - .build(); - - Category friend = Category.builder() - .categoryName("친구") - .build(); - - Category meeting = Category.builder() - .categoryName("회의") - .build(); - - categoryRepository.saveAll(List.of(school, friend, meeting)); + when(categoryService.getCategoryList()).thenReturn(CategoryDummy.getCategoryResponseDtoList()); // expected mockMvc.perform(get("/api/v1/categories")) .andExpectAll( status().isOk(), - content().contentType(MediaType.APPLICATION_JSON), jsonPath("$[?(@.categoryName == '학교')]").exists(), jsonPath("$[?(@.categoryName == '친구')]").exists(), - jsonPath("$[?(@.categoryName == '회의')]").exists() + jsonPath("$[?(@.categoryName == '팀플')]").exists(), + jsonPath("$[?(@.categoryName == '회의')]").exists(), + jsonPath("$[?(@.categoryName == '스터디')]").exists(), + jsonPath("$[?(@.categoryName == '취미')]").exists(), + jsonPath("$[?(@.categoryName == '봉사')]").exists(), + jsonPath("$[?(@.categoryName == '기타')]").exists() ) - .andDo(document("category/getCategoryList/success", - preprocessResponse(prettyPrint()), + .andDo(restDocs.document( responseFields( fieldWithPath("[].categoryId").description("카테고리 아이디"), fieldWithPath("[].categoryName").description("카테고리 이름") - ) - )); + )) + ); } @Test @DisplayName("카테고리 단건 조회 - 성공") - void testGetCategory_Success() throws Exception { + @JjakkakMockUser + void get_success() throws Exception { - // given - Category school = Category.builder() - .categoryName("기타") - .build(); + // given (1L, "학교") + when(categoryService.getCategory(anyLong())).thenReturn(CategoryDummy.getCategoryResponseDto()); - categoryRepository.save(school); // expected - mockMvc.perform(get("/api/v1/categories/{id}", school.getCategoryId())) + mockMvc.perform(get("/api/v1/categories/{id}", 1L)) .andExpectAll( status().isOk(), - jsonPath("$.categoryId").value(school.getCategoryId()), - jsonPath("$.categoryName").value("기타") + jsonPath("$.categoryId").value(1), + jsonPath("$.categoryName").value("학교") ) - .andDo(document("category/getCategory/success", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), + .andDo(restDocs.document( pathParameters( parameterWithName("id").description("카테고리 아이디")), responseFields( @@ -115,14 +92,17 @@ void testGetCategory_Success() throws Exception { @Test @DisplayName("카테고리 단건 조회 - 실패 (404)") - void testGetCategory_Fail() throws Exception { + @JjakkakMockUser + void get_fail() throws Exception { + + // given + when(categoryService.getCategory(anyLong())) + .thenThrow(new CategoryNotFoundException()); // expected - mockMvc.perform(get("/api/v1/categories/{id}", Long.MAX_VALUE)) + mockMvc.perform(get("/api/v1/categories/{id}", 100L)) .andExpect(status().isNotFound()) - .andDo(document("category/getCategory/fail-404", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()), + .andDo(restDocs.document( pathParameters( parameterWithName("id").description("카테고리 아이디")), responseFields( diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java index 2817b68..33d71d9 100644 --- a/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/MeetingDummy.java @@ -31,7 +31,6 @@ public static MeetingCreateRequestDto createRequestDto(List categoryIds) { ReflectionTestUtils.setField(requestDto, "meetingStartDate", LocalDate.of(2024, 7, 27)); ReflectionTestUtils.setField(requestDto, "meetingEndDate", LocalDate.of(2024, 7, 29)); ReflectionTestUtils.setField(requestDto, "numberOfPeople", 6); - ReflectionTestUtils.setField(requestDto, "isOnline", true); ReflectionTestUtils.setField(requestDto, "isAnonymous", false); ReflectionTestUtils.setField(requestDto, "voteEndDate", LocalDateTime.of(2024, 7, 26, 23, 59, 59)); ReflectionTestUtils.setField(requestDto, "categoryIds", categoryIds); @@ -62,6 +61,8 @@ public static MeetingResponseDto createResponseDto() { .numberOfPeople(6) .isAnonymous(false) .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) + .meetingLeaderId(1L) + .meetingUuid("1234ABCD") .build(); ReflectionTestUtils.setField(meeting, "meetingId", 1L); @@ -71,32 +72,4 @@ public static MeetingResponseDto createResponseDto() { .build(); } - /** - * Meeting 엔티티를 생성하여 반환합니다. - * - * @return Meeting 엔티티 리스트 - */ - public static List createMeetingList() { - - Meeting meeting = Meeting.builder() - .meetingName("DND 7조 회의") - .meetingStartDate(LocalDate.of(2024, 7, 27)) - .meetingEndDate(LocalDate.of(2024, 7, 29)) - .numberOfPeople(6) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 26, 23, 59, 59)) - .build(); - - Meeting study = Meeting.builder() - .meetingName("Java 스터디") - .meetingStartDate(LocalDate.of(2024, 8, 1)) - .meetingEndDate(LocalDate.of(2024, 8, 5)) - .numberOfPeople(4) - .isAnonymous(false) - .voteEndDate(LocalDateTime.of(2024, 7, 30, 23, 59, 59)) - .build(); - - return List.of(meeting, study); - } - } diff --git a/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java new file mode 100644 index 0000000..bbc9370 --- /dev/null +++ b/src/test/java/com/dnd/jjakkak/domain/meeting/controller/MeetingControllerTest.java @@ -0,0 +1,224 @@ +package com.dnd.jjakkak.domain.meeting.controller; + +import com.dnd.jjakkak.config.AbstractRestDocsTest; +import com.dnd.jjakkak.config.JjakkakMockUser; +import com.dnd.jjakkak.domain.meeting.MeetingDummy; +import com.dnd.jjakkak.domain.meeting.dto.request.MeetingConfirmRequestDto; +import com.dnd.jjakkak.domain.meeting.dto.request.MeetingCreateRequestDto; +import com.dnd.jjakkak.domain.meeting.exception.MeetingNotFoundException; +import com.dnd.jjakkak.domain.meeting.service.MeetingService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.snippet.Attributes.key; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * 모임 컨트롤러 테스트입니다. + * + * @author 정승조 + * @version 2024. 08. 05. + */ +@WebMvcTest(MeetingController.class) +@AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) +class MeetingControllerTest extends AbstractRestDocsTest { + + @MockBean + MeetingService meetingService; + + @Autowired + ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); + + + @Test + @DisplayName("모임 생성 테스트 - 성공") + @JjakkakMockUser + void create_success() throws Exception { + + MeetingCreateRequestDto requestDto = MeetingDummy.createRequestDto(List.of(1L, 2L)); + String json = objectMapper.writeValueAsString(requestDto); + + String token = "Bearer access_token"; + + mockMvc.perform(post("/api/v1/meeting") + .header("Authorization", token) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isCreated()) + .andDo(restDocs.document( + requestFields( + fieldWithPath("meetingName").description("모임 이름") + .attributes(key("constraint").value("모임 이름은 1자 이상 10자 이하로 입력해주세요.")), + fieldWithPath("meetingStartDate").description("모임 시작 날짜") + .attributes(key("constraint").value("모임 시작일은 종료일 이전이어야 합니다.")), + fieldWithPath("meetingEndDate").description("모임 종료 날짜") + .attributes(key("constraint").value("모임 종료일은 시작일 이후이어야 합니다.")), + fieldWithPath("numberOfPeople").description("모임 인원") + .attributes(key("constraint").value("모임 인원은 2명 이상 10명 이하로 설정해주세요.")), + fieldWithPath("isAnonymous").description("익명 여부") + .attributes(key("constraint").value("default = false (실명)")), + fieldWithPath("voteEndDate").description("투표 종료 날짜") + .attributes(key("constraint").value("투표 종료일은 모임 시작일 이전이어야 합니다.")), + fieldWithPath("categoryIds").description("카테고리 아이디 목록") + .attributes(key("constraint").value("1개 이상의 카테고리를 선택해주세요.")) + ))); + } + + @Test + @DisplayName("모임 생성 테스트 - 실패 (Invalid)") + @JjakkakMockUser + void create_fail_invalid() throws Exception { + MeetingCreateRequestDto requestDto = MeetingDummy.createInvalidRequestDto(); + String json = objectMapper.writeValueAsString(requestDto); + + String token = "Bearer access_token"; + + mockMvc.perform(post("/api/v1/meeting") + .header("Authorization", token) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isBadRequest()) + .andDo(restDocs.document( + responseFields( + fieldWithPath("code").description("상태 코드"), + fieldWithPath("message").description("에러 메시지"), + fieldWithPath("validation").description("유효성 검사 오류 목록"), + fieldWithPath("validation.meetingName").description("모임명은 필수 값입니다."), + fieldWithPath("validation.meetingStartDate").description("모임 일정 시작일은 필수 값입니다."), + fieldWithPath("validation.meetingEndDate").description("모임 일정 종료일은 필수 값입니다."), + fieldWithPath("validation.numberOfPeople").description("인원수는 필수 값입니다."), + fieldWithPath("validation.isAnonymous").description("익명 여부는 필수 값입니다."), + fieldWithPath("validation.voteEndDate").description("투표 종료일은 필수 값입니다."), + fieldWithPath("validation.categoryIds").description("카테고리는 최소 1개 이상 8개 이하로 선택해주세요.") + ))); + + } + + @Test + @DisplayName("모임 조회 테스트") + @JjakkakMockUser + void get_byUuid() throws Exception { + + // given + String uuid = "1234ABCD"; + when(meetingService.getMeetingByUuid(anyString())).thenReturn(MeetingDummy.createResponseDto()); + + // expected + + mockMvc.perform(get("/api/v1/meeting/{meetingUuid}", uuid) + .contentType(MediaType.APPLICATION_JSON)) + .andExpectAll( + status().isOk(), + jsonPath("$.meetingId").value(1L), + jsonPath("$.meetingName").value("세븐일레븐"), + jsonPath("$.meetingStartDate").value("2024-07-27"), + jsonPath("$.meetingEndDate").value("2024-07-29"), + jsonPath("$.numberOfPeople").value(6), + jsonPath("$.isAnonymous").value(false), + jsonPath("$.voteEndDate").value("2024-07-26T23:59:59"), + jsonPath("$.confirmedSchedule").doesNotExist(), // null + jsonPath("$.meetingLeaderId").value(1L), + jsonPath("$.meetingUuid").value("1234ABCD") + ) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingUuid").description("모임 UUID")), + responseFields( + fieldWithPath("meetingId").description("모임 ID"), + fieldWithPath("meetingName").description("모임 이름"), + fieldWithPath("meetingStartDate").description("모임 시작 날짜"), + fieldWithPath("meetingEndDate").description("모임 종료 날짜"), + fieldWithPath("numberOfPeople").description("모임 인원"), + fieldWithPath("isAnonymous").description("익명 여부"), + fieldWithPath("voteEndDate").description("투표 종료 날짜"), + fieldWithPath("confirmedSchedule").description("확정된 일정"), + fieldWithPath("meetingLeaderId").description("모임 리더 ID"), + fieldWithPath("meetingUuid").description("모임 UUID") + ))); + } + + @Test + @DisplayName("모임 조회 테스트 - 실패") + @JjakkakMockUser + void get_byUuid_fail() throws Exception { + + // given + when(meetingService.getMeetingByUuid(anyString())) + .thenThrow(new MeetingNotFoundException()); + + // expected + mockMvc.perform(get("/api/v1/meeting/{meetingUuid}", "ABCD1234") + .contentType(MediaType.APPLICATION_JSON)) + .andExpectAll( + status().isNotFound(), + jsonPath("$.code").value(404), + jsonPath("$.message").value("모임을 찾을 수 없습니다.") + ) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingUuid").description("모임 UUID")), + responseFields( + fieldWithPath("code").description("에러 코드"), + fieldWithPath("message").description("에러 메시지"), + fieldWithPath("validation").description("유효성 검사 오류 목록") + ))); + } + + @Test + @JjakkakMockUser + @DisplayName("모임 확정 일정 수정 테스트") + void update_confirmedSchedule() throws Exception { + + // given + MeetingConfirmRequestDto requestDto = new MeetingConfirmRequestDto(); + ReflectionTestUtils.setField(requestDto, "confirmedSchedule", LocalDateTime.of(2024, 7, 28, 12, 30)); + + String json = objectMapper.writeValueAsString(requestDto); + + // expected + mockMvc.perform(patch("/api/v1/meeting/{meetingId}/confirm", 1L) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingId").description("모임 ID")), + requestFields( + fieldWithPath("confirmedSchedule").description("확정된 일정") + .attributes(key("constraint").value("확정된 일정은 시작일과 종료일 사이의 날짜여야 합니다."))) + )); + } + + @Test + @DisplayName("모임 삭제 테스트") + @JjakkakMockUser + void delete_success() throws Exception { + + // expected + mockMvc.perform(delete("/api/v1/meeting/{meetingId}", 1L) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("meetingId").description("모임 ID")) + )); + } +} \ No newline at end of file From 0373261542e730463eab85ac4c7a5c891c6f75a1 Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 6 Aug 2024 17:46:41 +0900 Subject: [PATCH 46/49] =?UTF-8?q?fix:=20=EB=B3=91=ED=95=A9=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dnd/jjakkak/config/AbstractRestDocsTest.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java index 3278e21..6ef6a98 100644 --- a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java +++ b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java @@ -31,18 +31,22 @@ @ExtendWith(RestDocumentationExtension.class) public abstract class AbstractRestDocsTest { - @Autowired - protected RestDocumentationResultHandler restDocs; - @Autowired - protected MockMvc mockMvc; /** * jwtAuthentication Filter 내부에서 사용하는 Bean 등록 */ @MockBean JwtProvider jwtProvider; + @MockBean MemberRepository memberRepository; + + @Autowired + protected RestDocumentationResultHandler restDocs; + + @Autowired + protected MockMvc mockMvc; + @BeforeEach void setUp(WebApplicationContext context, RestDocumentationContextProvider restDocumentation) { @@ -60,4 +64,4 @@ void setUp(WebApplicationContext context, RestDocumentationContextProvider restD .defaultRequest(delete("/**").with(csrf())) .build(); } -} +} \ No newline at end of file From 5f8b4dd8062fbf48765f678745ff0f42e411527a Mon Sep 17 00:00:00 2001 From: RTUnu12 Date: Tue, 6 Aug 2024 17:48:33 +0900 Subject: [PATCH 47/49] =?UTF-8?q?fix:=20=EB=B3=91=ED=95=A9=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20:=20JWT?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/exception/AccessTokenExpiredException.java | 2 +- .../jwt/exception/MalformedTokenException.java | 2 +- .../{member => }/jwt/filter/JwtAuthenticationFilter.java | 8 ++++---- .../{member => }/jwt/handler/OAuth2FailureHandler.java | 2 +- .../{member => }/jwt/handler/OAuth2LogoutHandler.java | 2 +- .../{member => }/jwt/handler/OAuth2SuccessHandler.java | 4 ++-- .../domain/{member => }/jwt/provider/JwtProvider.java | 2 +- .../jjakkak/domain/member/controller/AuthController.java | 2 +- .../jjakkak/global/config/security/SecurityConfig.java | 8 ++++---- .../java/com/dnd/jjakkak/config/AbstractRestDocsTest.java | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/exception/AccessTokenExpiredException.java (86%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/exception/MalformedTokenException.java (86%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/filter/JwtAuthenticationFilter.java (93%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/handler/OAuth2FailureHandler.java (96%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/handler/OAuth2LogoutHandler.java (98%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/handler/OAuth2SuccessHandler.java (95%) rename src/main/java/com/dnd/jjakkak/domain/{member => }/jwt/provider/JwtProvider.java (98%) diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/AccessTokenExpiredException.java similarity index 86% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/exception/AccessTokenExpiredException.java index 08fa067..4dd34fb 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/AccessTokenExpiredException.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/AccessTokenExpiredException.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.exception; +package com.dnd.jjakkak.domain.jwt.exception; import com.dnd.jjakkak.global.exception.GeneralException; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/MalformedTokenException.java similarity index 86% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/exception/MalformedTokenException.java index 9f57d5b..759aa7a 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/exception/MalformedTokenException.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/exception/MalformedTokenException.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.exception; +package com.dnd.jjakkak.domain.jwt.exception; import com.dnd.jjakkak.global.exception.GeneralException; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java similarity index 93% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java index c9274f8..b730c04 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java @@ -1,11 +1,11 @@ -package com.dnd.jjakkak.domain.member.jwt.filter; +package com.dnd.jjakkak.domain.jwt.filter; +import com.dnd.jjakkak.domain.jwt.exception.AccessTokenExpiredException; +import com.dnd.jjakkak.domain.jwt.exception.MalformedTokenException; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.entity.Member; import com.dnd.jjakkak.domain.member.entity.Role; import com.dnd.jjakkak.domain.member.exception.MemberNotFoundException; -import com.dnd.jjakkak.domain.member.jwt.exception.AccessTokenExpiredException; -import com.dnd.jjakkak.domain.member.jwt.exception.MalformedTokenException; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2FailureHandler.java similarity index 96% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2FailureHandler.java index c4b4a24..516c1e3 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2FailureHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2FailureHandler.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.handler; +package com.dnd.jjakkak.domain.jwt.handler; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java similarity index 98% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java index 2efd3c2..93b7530 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2LogoutHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.handler; +package com.dnd.jjakkak.domain.jwt.handler; import com.dnd.jjakkak.domain.member.service.BlacklistService; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java similarity index 95% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java index a2b4172..8fb1b97 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java @@ -1,7 +1,7 @@ -package com.dnd.jjakkak.domain.member.jwt.handler; +package com.dnd.jjakkak.domain.jwt.handler; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.entity.Member; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java b/src/main/java/com/dnd/jjakkak/domain/jwt/provider/JwtProvider.java similarity index 98% rename from src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java rename to src/main/java/com/dnd/jjakkak/domain/jwt/provider/JwtProvider.java index 42f594a..37c19c1 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/jwt/provider/JwtProvider.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/provider/JwtProvider.java @@ -1,4 +1,4 @@ -package com.dnd.jjakkak.domain.member.jwt.provider; +package com.dnd.jjakkak.domain.jwt.provider; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; diff --git a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java index ecddf46..3420527 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java @@ -1,6 +1,6 @@ package com.dnd.jjakkak.domain.member.controller; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java index 6736e77..5b01968 100644 --- a/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java +++ b/src/main/java/com/dnd/jjakkak/global/config/security/SecurityConfig.java @@ -1,9 +1,9 @@ package com.dnd.jjakkak.global.config.security; -import com.dnd.jjakkak.domain.member.jwt.filter.JwtAuthenticationFilter; -import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2FailureHandler; -import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2LogoutHandler; -import com.dnd.jjakkak.domain.member.jwt.handler.OAuth2SuccessHandler; +import com.dnd.jjakkak.domain.jwt.filter.JwtAuthenticationFilter; +import com.dnd.jjakkak.domain.jwt.handler.OAuth2FailureHandler; +import com.dnd.jjakkak.domain.jwt.handler.OAuth2LogoutHandler; +import com.dnd.jjakkak.domain.jwt.handler.OAuth2SuccessHandler; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Configurable; diff --git a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java index ea49d29..aa7bc43 100644 --- a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java +++ b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java @@ -1,6 +1,6 @@ package com.dnd.jjakkak.config; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; import com.dnd.jjakkak.domain.member.repository.MemberRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; From e399409fac5856c3f29a922f53de29135fb16170 Mon Sep 17 00:00:00 2001 From: seungjo Date: Tue, 6 Aug 2024 19:12:41 +0900 Subject: [PATCH 48/49] =?UTF-8?q?test:=20Controller=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/static/index.html | 6 +- .../jjakkak/config/AbstractRestDocsTest.java | 4 +- .../member/controller/AuthControllerTest.java | 64 ++++++------------- .../controller/MemberControllerTest.java | 57 ++++------------- 4 files changed, 34 insertions(+), 97 deletions(-) diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index ce96cb5..2025c5a 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -573,7 +573,7 @@

    1.1. 카테고리 목록 조회

    [ { "categoryId" : 1, - "categoryName" : "기타" + "categoryName" : "학교" }, { "categoryId" : 2, "categoryName" : "학교" @@ -660,7 +660,7 @@

    1.2. 카테고리 개별 조회 - { "categoryId" : 1, - "categoryName" : "기타" + "categoryName" : "학교" }

    @@ -894,7 +894,7 @@

    2.9. 모임 삭제 - 실패

    diff --git a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java index 6ef6a98..91369aa 100644 --- a/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java +++ b/src/test/java/com/dnd/jjakkak/config/AbstractRestDocsTest.java @@ -35,10 +35,10 @@ public abstract class AbstractRestDocsTest { * jwtAuthentication Filter 내부에서 사용하는 Bean 등록 */ @MockBean - JwtProvider jwtProvider; + protected JwtProvider jwtProvider; @MockBean - MemberRepository memberRepository; + protected MemberRepository memberRepository; @Autowired diff --git a/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java index 9ab568f..c168470 100644 --- a/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/member/controller/AuthControllerTest.java @@ -1,26 +1,17 @@ package com.dnd.jjakkak.domain.member.controller; -import com.dnd.jjakkak.domain.member.jwt.filter.JwtAuthenticationFilter; -import com.dnd.jjakkak.domain.member.jwt.provider.JwtProvider; -import com.dnd.jjakkak.domain.member.repository.MemberRepository; -import org.junit.jupiter.api.BeforeEach; +import com.dnd.jjakkak.config.AbstractRestDocsTest; +import com.dnd.jjakkak.config.JjakkakMockUser; +import com.dnd.jjakkak.domain.jwt.filter.JwtAuthenticationFilter; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -31,56 +22,37 @@ * @author 류태웅 * @version 2024. 08. 06 */ -@ActiveProfiles("test") @WebMvcTest(AuthController.class) @AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) -@AutoConfigureMockMvc -class AuthControllerTest { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private JwtProvider jwtProvider; +class AuthControllerTest extends AbstractRestDocsTest { @MockBean - private JwtAuthenticationFilter jwtAuthenticationFilter; - - @MockBean - private MemberRepository memberRepository; - - @BeforeEach - public void setUp(WebApplicationContext webApplicationContext) { - this.mockMvc = MockMvcBuilders - .webAppContextSetup(webApplicationContext) - .apply(springSecurity()) - .defaultRequest(get("/**").with(csrf())) - .build(); - - } + JwtAuthenticationFilter jwtAuthenticationFilter; @Test @DisplayName("로그인 상태 확인 - 확인됨") - //@JjakkakMockUser - public void testCheckAuthAuthenticated() throws Exception { + @JjakkakMockUser + void testCheckAuthAuthenticated() throws Exception { + // JwtProvider의 validate 메소드가 "user"를 반환하도록 설정 when(jwtProvider.validate(anyString())).thenReturn("user"); - mockMvc.perform(get("/check-auth") - .header("Authorization", "Bearer valid-token") - .accept(MediaType.APPLICATION_JSON)) + mockMvc.perform(get("/api/v1/check-auth") + .header("Authorization", "Bearer valid-token") + .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.isAuthenticated").value(true)); } @Test @DisplayName("로그인 상태 확인 - 토큰이 인증되질 않음") - //@JjakkakMockUser - public void testCheckAuthNotAuthenticated() throws Exception { + @JjakkakMockUser + void testCheckAuthNotAuthenticated() throws Exception { + // JwtProvider의 validate 메소드가 null을 반환하도록 설정 when(jwtProvider.validate(anyString())).thenReturn(null); - mockMvc.perform(get("/check-auth") + mockMvc.perform(get("/api/v1/check-auth") .header("Authorization", "Bearer invalid-token") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) @@ -89,9 +61,9 @@ public void testCheckAuthNotAuthenticated() throws Exception { @Test @DisplayName("로그인 상태 확인 - 토큰 없음") - //@JjakkakMockUser - public void testCheckAuthNoToken() throws Exception { - mockMvc.perform(get("/check-auth") + @JjakkakMockUser + void testCheckAuthNoToken() throws Exception { + mockMvc.perform(get("/api/v1/check-auth") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.isAuthenticated").value(false)); diff --git a/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java b/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java index 28d0f15..3b12344 100644 --- a/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/dnd/jjakkak/domain/member/controller/MemberControllerTest.java @@ -1,26 +1,18 @@ package com.dnd.jjakkak.domain.member.controller; -import com.dnd.jjakkak.domain.jwt.filter.JwtAuthenticationFilter; -import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; +import com.dnd.jjakkak.config.AbstractRestDocsTest; import com.dnd.jjakkak.domain.meeting.dto.response.MeetingResponseDto; import com.dnd.jjakkak.domain.meeting.entity.Meeting; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateNicknameRequestDto; import com.dnd.jjakkak.domain.member.dto.request.MemberUpdateProfileRequestDto; import com.dnd.jjakkak.domain.member.service.MemberService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.context.WebApplicationContext; import java.time.LocalDate; import java.time.LocalDateTime; @@ -30,10 +22,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -43,37 +32,17 @@ * @author 류태웅 * @version 2024. 08. 06 */ - -@ActiveProfiles("test") @WebMvcTest(MemberController.class) @AutoConfigureRestDocs(uriHost = "43.202.65.170.nip.io", uriPort = 80) -@AutoConfigureMockMvc -public class MemberControllerTest { - @Autowired - private MockMvc mockMvc; - - @MockBean - private MemberService memberService; +class MemberControllerTest extends AbstractRestDocsTest { @MockBean - private JwtAuthenticationFilter jwtAuthenticationFilter; - - @MockBean - private JwtProvider jwtProvider; - - @BeforeEach - public void setUp(WebApplicationContext webApplicationContext) { - this.mockMvc = MockMvcBuilders - .webAppContextSetup(webApplicationContext) - .apply(springSecurity()) - .defaultRequest(get("/**").with(csrf())) - .build(); - } + MemberService memberService; @Test @WithMockUser @DisplayName("회원 모임 리스트 조회") - public void testGetMemberListByMemberId() throws Exception { + void testGetMemberListByMemberId() throws Exception { Meeting meeting = Meeting.builder() .meetingName("Test Meeting") .meetingStartDate(LocalDate.now()) @@ -101,47 +70,43 @@ public void testGetMemberListByMemberId() throws Exception { .andExpect(jsonPath("$[0].isAnonymous").value(false)) .andExpect(jsonPath("$[0].voteEndDate").exists()) .andExpect(jsonPath("$[0].meetingLeaderId").value(1L)) - .andExpect(jsonPath("$[0].meetingUuid").value("uuid")) - .andDo(print()); + .andExpect(jsonPath("$[0].meetingUuid").value("uuid")); } @Test @WithMockUser @DisplayName("회원 닉네임 업데이트") - public void testUpdateNickname() throws Exception { + void testUpdateNickname() throws Exception { doNothing().when(memberService).updateNickname(anyLong(), any(MemberUpdateNicknameRequestDto.class)); mockMvc.perform(patch("/api/v1/member/{memberId}/nickname", 9L) .contentType(MediaType.APPLICATION_JSON) .content("{\"memberNickname\": \"newName\"}") .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()); + .andExpect(status().isOk()); } @Test @WithMockUser @DisplayName("회원 프로필 업데이트") - public void testUpdateProfile() throws Exception { + void testUpdateProfile() throws Exception { doNothing().when(memberService).updateProfile(anyLong(), any(MemberUpdateProfileRequestDto.class)); mockMvc.perform(patch("/api/v1/member/{memberId}/profile", 9L) .contentType(MediaType.APPLICATION_JSON) .content("{\"memberProfile\": \"http://newProfile\"}") .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()); + .andExpect(status().isOk()); } @Test @WithMockUser @DisplayName("회원 삭제") - public void testDeleteMember() throws Exception { + void testDeleteMember() throws Exception { doNothing().when(memberService).deleteMember(anyLong()); mockMvc.perform(delete("/api/v1/member/{memberId}", 9L) .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andDo(print()); + .andExpect(status().isOk()); } } From 11738dd2d225a5634de2a9885cede2bdf85b0f5d Mon Sep 17 00:00:00 2001 From: seungjo Date: Wed, 7 Aug 2024 13:36:49 +0900 Subject: [PATCH 49/49] =?UTF-8?q?refactor:=20log=20level=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java | 6 +++--- .../dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java | 6 +++--- .../jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java | 6 +++--- .../jjakkak/domain/member/controller/AuthController.java | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java index c27dd4c..9f22a78 100644 --- a/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/filter/JwtAuthenticationFilter.java @@ -50,11 +50,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String path = request.getRequestURI(); if(PatternMatchUtils.simpleMatch(SecurityConfig.WHITE_LIST, path)){ - log.info("path: {} -> passed token filter", path); + log.debug("path: {} -> passed token filter", path); filterChain.doFilter(request, response); } String token = parseBearerToken(request); - log.info("도착한 토큰: {}", token); + log.debug("도착한 토큰: {}", token); if (token == null) { // Bearer 인증 방식이 아니거나 빈 값일 경우 진행하지 말고 다음 필터로 바로 넘김 filterChain.doFilter(request, response); return; @@ -62,7 +62,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String kakaoId; try { kakaoId = jwtProvider.validate(token); - log.info("검증된 카카오 ID: {}", kakaoId); + log.debug("검증된 카카오 ID: {}", kakaoId); } catch (ExpiredJwtException e) { log.error("엑세스 토큰이 만료됨", e); throw new AccessTokenExpiredException(); diff --git a/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java index 93b7530..43a0040 100644 --- a/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2LogoutHandler.java @@ -39,17 +39,17 @@ public void logout(HttpServletRequest request, HttpServletResponse response, Aut } } - log.info("logout 시작"); + log.debug("logout 시작"); if (refreshToken != null) { - log.info("logout refreshToken: {}", refreshToken); + log.debug("logout refreshToken: {}", refreshToken); if (refreshTokenService.validateRefreshToken(refreshToken)) { try { refreshTokenService.deleteRefreshToken(refreshToken); LocalDateTime expirationDate = LocalDateTime.now().plusWeeks(1); blacklistService.blacklistToken(refreshToken, expirationDate); - log.info("logout 성공"); + log.debug("logout 성공"); response.setStatus(HttpServletResponse.SC_OK); } catch (Exception e) { log.error("서버 에러", e); diff --git a/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java index b654e0b..dc74bca 100644 --- a/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/dnd/jjakkak/domain/jwt/handler/OAuth2SuccessHandler.java @@ -1,7 +1,7 @@ package com.dnd.jjakkak.domain.jwt.handler; -import com.dnd.jjakkak.domain.member.entity.Member; import com.dnd.jjakkak.domain.jwt.provider.JwtProvider; +import com.dnd.jjakkak.domain.member.entity.Member; import com.dnd.jjakkak.domain.member.service.RefreshTokenService; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; @@ -40,8 +40,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String refreshToken = jwtProvider.createRefreshToken(kakaoId); refreshTokenService.createRefreshToken(oauth2User.getMemberId(), refreshToken); - log.info("access token: " + accessToken); - log.info("refresh token: " + refreshToken); + log.debug("access token: " + accessToken); + log.debug("refresh token: " + refreshToken); // Refresh Token 쿠키 설정 Cookie refreshTokenCookie = new Cookie("refresh_token", refreshToken); diff --git a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java index 14216a1..ad6cf40 100644 --- a/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java +++ b/src/main/java/com/dnd/jjakkak/domain/member/controller/AuthController.java @@ -37,17 +37,17 @@ public class AuthController { @GetMapping("/check-auth") public ResponseEntity checkAuth(HttpServletRequest request) { String authorizationHeader = request.getHeader("Authorization"); - log.info(authorizationHeader); + log.debug(authorizationHeader); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { String token = authorizationHeader.substring(7); String subject = jwtProvider.validate(token); if (subject != null && !subject.isEmpty()) { - log.info("isAuthenticated"); + log.debug("isAuthenticated"); return ResponseEntity.ok().body(Collections.singletonMap("isAuthenticated", true)); } } - log.info("isNotAuthenticated"); + log.debug("isNotAuthenticated"); return ResponseEntity.ok().body(Collections.singletonMap("isAuthenticated", false)); } }