From 2d40dc6e67a60f47290d32853371f2aabbfc3845 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 29 Sep 2025 18:20:16 +0900 Subject: [PATCH 001/114] =?UTF-8?q?fix=20:=20=EC=A0=9C=EB=AA=A9=20,?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20NotBlank=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leets/weeth/domain/board/application/dto/PostDTO.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java b/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java index ced6270e..6fd4f944 100644 --- a/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java +++ b/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java @@ -1,6 +1,7 @@ package leets.weeth.domain.board.application.dto; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.List; @@ -17,8 +18,8 @@ public class PostDTO { @Builder public record Save( - @NotNull String title, - @NotNull String content, + @NotBlank(message = "제목 입력은 필수입니다.") String title, + @NotBlank(message = "내용 입력은 필수입니다.") String content, @NotNull Category category, String studyName, int week, From a974c20cb3b2e5b97f3e387c9ffe49c25e498d35 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 29 Sep 2025 18:38:51 +0900 Subject: [PATCH 002/114] =?UTF-8?q?add=20:=20Size=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20=EA=B8=80?= =?UTF-8?q?=EC=9E=90=EC=88=98=20300=EC=9E=90=EB=A1=9C=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/domain/comment/application/dto/CommentDTO.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/leets/weeth/domain/comment/application/dto/CommentDTO.java b/src/main/java/leets/weeth/domain/comment/application/dto/CommentDTO.java index 639e377d..25d124c3 100644 --- a/src/main/java/leets/weeth/domain/comment/application/dto/CommentDTO.java +++ b/src/main/java/leets/weeth/domain/comment/application/dto/CommentDTO.java @@ -3,6 +3,7 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import leets.weeth.domain.file.application.dto.request.FileSaveRequest; import leets.weeth.domain.file.application.dto.response.FileResponse; import leets.weeth.domain.user.domain.entity.enums.Position; @@ -17,13 +18,13 @@ public class CommentDTO { @Builder public record Save( Long parentCommentId, - @NotBlank String content, + @NotBlank @Size(max=300, message = "댓글은 최대 300자까지 가능합니다.") String content, @Valid List<@NotNull FileSaveRequest> files ){} @Builder public record Update( - @NotBlank String content, + @NotBlank @Size(max=300, message = "댓글은 최대 300자까지 가능합니다.") String content, @Valid List<@NotNull FileSaveRequest> files ){} From 979da864418a7fa6ab6843ddc77517de66d0a413 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 29 Sep 2025 18:41:15 +0900 Subject: [PATCH 003/114] =?UTF-8?q?=20add=20:=20@Column=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B8=EC=9D=B4=20300=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/leets/weeth/domain/comment/domain/entity/Comment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/leets/weeth/domain/comment/domain/entity/Comment.java b/src/main/java/leets/weeth/domain/comment/domain/entity/Comment.java index db791dbb..dde41624 100644 --- a/src/main/java/leets/weeth/domain/comment/domain/entity/Comment.java +++ b/src/main/java/leets/weeth/domain/comment/domain/entity/Comment.java @@ -27,6 +27,7 @@ public class Comment extends BaseEntity { @Column(name = "comment_id") private Long id; + @Column(length = 300) private String content; @Column(nullable = false) From 25c3341af122cc87a274f3c216c5f1d41b241644 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 11 Oct 2025 21:04:44 +0900 Subject: [PATCH 004/114] =?UTF-8?q?chore:=20=ED=94=84=EB=A1=9C=EB=A9=94?= =?UTF-8?q?=ED=85=8C=EC=9A=B0=EC=8A=A4=20=EB=B9=8C=EB=93=9C=20=EB=B0=8F=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 ++++ src/main/resources/application.yml | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/build.gradle b/build.gradle index dd370c44..15af9351 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,10 @@ dependencies { // Spring Authorization Server implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server' + // Prometheus + implementation 'org.springframework.boot:spring-boot-starter-actuator' + runtimeOnly 'io.micrometer:micrometer-registry-prometheus' + } tasks.named('test') { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3de76ac2..c93693d6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -39,3 +39,13 @@ auth: scopes: ${LEENK_SCOPES} accessTokenTtl: ${LEENK_ACCESS_TOKEN_TTL} refreshTokenTtl: ${LEENK_REFRESH_TOKEN_TTL} + +management: + endpoints: + web: + exposure: + include: "*" + prometheus: + metrics: + export: + enabled: true From 041aa537c2897b515b1c209533723a753795226b Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 16 Oct 2025 20:38:23 +0900 Subject: [PATCH 005/114] =?UTF-8?q?chore:=20/actuator/prometheus=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EC=9D=B8=EC=A6=9D=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/leets/weeth/global/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/leets/weeth/global/config/SecurityConfig.java b/src/main/java/leets/weeth/global/config/SecurityConfig.java index d8e245f5..c1dcab2a 100644 --- a/src/main/java/leets/weeth/global/config/SecurityConfig.java +++ b/src/main/java/leets/weeth/global/config/SecurityConfig.java @@ -80,6 +80,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/js/**", "/img/**", "/scss/**", "/vendor/**").permitAll() // 스웨거 경로 .requestMatchers("/v3/api-docs", "/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/swagger/**").permitAll() + .requestMatchers("/actuator/prometheus").permitAll() .requestMatchers("/api/v1/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) From ddc6b22a32c5c7b4afe133d35f8d75d295d222ed Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 16 Oct 2025 20:38:59 +0900 Subject: [PATCH 006/114] =?UTF-8?q?feat:=20TokenNotFoundException=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=20=EC=83=9D?= =?UTF-8?q?=EB=9E=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/jwt/filter/JwtAuthenticationProcessingFilter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java b/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java index 4097fca9..d30176c8 100644 --- a/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java +++ b/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java @@ -47,6 +47,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (jwtProvider.validate(accessToken)) { saveAuthentication(accessToken); } + } catch (TokenNotFoundException e) { + // 로그 생략 } catch (RuntimeException e) { log.info("error token: {}", e.getMessage()); } From 0a62cf62469d2f3253e6bad1ef8fa452b79eea0b Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 17 Oct 2025 16:28:02 +0900 Subject: [PATCH 007/114] =?UTF-8?q?feat:=20TokenNotFoundException=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=20=EC=8B=9C=20debug=20=EB=A1=9C=EA=B7=B8=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 --- .../auth/jwt/filter/JwtAuthenticationProcessingFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java b/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java index d30176c8..1e78d754 100644 --- a/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java +++ b/src/main/java/leets/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.java @@ -48,7 +48,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse saveAuthentication(accessToken); } } catch (TokenNotFoundException e) { - // 로그 생략 + log.debug("Token not found: {}", e.getMessage()); } catch (RuntimeException e) { log.info("error token: {}", e.getMessage()); } From 614b0fced25b100c8ed926165a3137eb22eb099a Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 17 Oct 2025 16:28:41 +0900 Subject: [PATCH 008/114] =?UTF-8?q?chore:=20actuator=20=EB=85=B8=EC=B6=9C?= =?UTF-8?q?=20endpoint=EB=A5=BC=20health,prometheus=EB=A1=9C=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c93693d6..37cccbfe 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -44,7 +44,7 @@ management: endpoints: web: exposure: - include: "*" + include: "health,prometheus" prometheus: metrics: export: From 542326bd2c1c9f45c5d738ae24b8b9ecdf5c1c70 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sat, 18 Oct 2025 18:55:54 +0900 Subject: [PATCH 009/114] =?UTF-8?q?test=20:=20CardinalUseCase=5Fsave()?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/CardinalUseCaseTest.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java new file mode 100644 index 00000000..8a2fd892 --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java @@ -0,0 +1,97 @@ +package leets.weeth.domain.user.application.usecase; + +import static org.mockito.BDDMockito.*; + +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 leets.weeth.domain.user.application.dto.request.CardinalSaveRequest; +import leets.weeth.domain.user.application.mapper.CardinalMapper; +import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.entity.enums.CardinalStatus; +import leets.weeth.domain.user.domain.service.CardinalGetService; +import leets.weeth.domain.user.domain.service.CardinalSaveService; + +@ExtendWith(MockitoExtension.class) +public class CardinalUseCaseTest { + // 실제 CardinalUseCase에서 사용하는 의존성을 Mock 객체로 대신 주입 + @Mock + private CardinalGetService cardinalGetService; + + @Mock + private CardinalSaveService cardinalSaveService; + + @Mock + private CardinalMapper cardinalMapper; + + @InjectMocks + private CardinalUseCase useCase; + + // Given-When-Then 패턴을 쉽게 이해하기 위해 메서드명_상황_예상결과로 테스트 메서드 네이밍 + + + @Test // 진행중이 아닌 기수를 등록하는 경우 + void save_진행중이_아닌_기수라면_검증후_저장만() { + //given + var request = new CardinalSaveRequest(7, 2025,1,false); + + var toSave = Cardinal.builder() //DB저장되기 전의 객체 + .cardinalNumber(7) + .year(2025) + .semester(1) + .build(); + + var saved = Cardinal.builder()// 저장되고 난 후 반환된 객체 + .cardinalNumber(7) + .year(2025) + .semester(1) + .status(CardinalStatus.DONE) + .build(); + + willDoNothing().given(cardinalGetService).validateCardinal(7); + given(cardinalMapper.from(request)).willReturn(toSave); + given(cardinalSaveService.save(toSave)).willReturn(saved); + + //when + useCase.save(request); + + //then + then(cardinalGetService).should().validateCardinal(7); + then(cardinalSaveService).should().save(toSave); + then(cardinalGetService).should(never()).findInProgress(); + } + + @Test + void save_새_기수가_진행중이라면_기존_기수는_done_현재기수는_inProgress() { + //given + //when + //then + } + + @Test + void update_진행상태가_변하지_않는다면_단순_업데이트만() { + //given + //when + //then + } + + @Test + void update_진행중으로_변경되면_기존은_done처리_현재는_inProgress() { + //given + //when + //then + } + + @Test + void finaALl_조회된_모든_기수를_DTO로_매핑처리() { + + } + + + + + +} From a32b19e47b5e9f55cf5cb0f143453dd702c13a4d Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sat, 18 Oct 2025 20:15:42 +0900 Subject: [PATCH 010/114] =?UTF-8?q?test=20:=20=EC=83=88=20=EA=B8=B0?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EC=A0=80=EC=9E=A5=ED=95=A8=EA=B3=BC=20?= =?UTF-8?q?=EB=8F=99=EC=8B=9C=EC=97=90=20InProgress=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=84=20=EA=B8=B0=EC=88=98=20DONE=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=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 --- .../usecase/CardinalUseCaseTest.java | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java index 8a2fd892..a3d98334 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java @@ -1,7 +1,11 @@ package leets.weeth.domain.user.application.usecase; +import static org.assertj.core.api.AssertionsForClassTypes.*; import static org.mockito.BDDMockito.*; +import java.util.List; +import java.util.Optional; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -66,11 +70,47 @@ public class CardinalUseCaseTest { @Test void save_새_기수가_진행중이라면_기존_기수는_done_현재기수는_inProgress() { - //given - //when - //then + // given + var request = new CardinalSaveRequest(7, 2025,1,true); + + var oldCardinal = Cardinal.builder() + .cardinalNumber(6) + .year(2024) + .semester(2) + .status(CardinalStatus.IN_PROGRESS) + .build(); + + var newCardinalBeforeSave = Cardinal.builder() + .cardinalNumber(7) + .year(2025) + .semester(1) + .status(CardinalStatus.DONE) + .build(); + + var newCardinalAfterSave = Cardinal.builder() + .cardinalNumber(7) + .year(2025) + .semester(1) + .status(CardinalStatus.IN_PROGRESS) + .build(); + + + given(cardinalGetService.findInProgress()).willReturn(List.of(oldCardinal)); + given(cardinalMapper.from(request)).willReturn(newCardinalBeforeSave); + given(cardinalSaveService.save(newCardinalBeforeSave)).willReturn(newCardinalAfterSave); + + // when + useCase.save(request); + + // then + then(cardinalGetService).should().findInProgress(); + then(cardinalSaveService).should().save(newCardinalBeforeSave); + + assertThat(oldCardinal.getStatus()).isEqualTo(CardinalStatus.DONE); + assertThat(newCardinalAfterSave.getStatus()).isEqualTo(CardinalStatus.IN_PROGRESS); } + @Test void update_진행상태가_변하지_않는다면_단순_업데이트만() { //given From 11179d388d63b9aaa16808624eb893d8e20d95c4 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sat, 18 Oct 2025 20:28:09 +0900 Subject: [PATCH 011/114] =?UTF-8?q?test=20:=20CardinalUseCase=20-=20update?= =?UTF-8?q?()=20=EC=97=B0=EB=8F=84/=ED=95=99=EA=B8=B0=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../usecase/CardinalUseCaseTest.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java index a3d98334..a5422b23 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java @@ -13,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import leets.weeth.domain.user.application.dto.request.CardinalSaveRequest; +import leets.weeth.domain.user.application.dto.request.CardinalUpdateRequest; import leets.weeth.domain.user.application.mapper.CardinalMapper; import leets.weeth.domain.user.domain.entity.Cardinal; import leets.weeth.domain.user.domain.entity.enums.CardinalStatus; @@ -112,19 +113,23 @@ public class CardinalUseCaseTest { @Test - void update_진행상태가_변하지_않는다면_단순_업데이트만() { + void update_연도와_학기를_변경한다() { //given - //when - //then - } + var cardinal = Cardinal.builder() + .year(2024) + .semester(2) + .build(); + var dto = new CardinalUpdateRequest(1L, 2025,1,false); - @Test - void update_진행중으로_변경되면_기존은_done처리_현재는_inProgress() { - //given //when + cardinal.update(dto); + //then + assertThat(cardinal.getYear()).isEqualTo(2025); + assertThat(cardinal.getSemester()).isEqualTo(1); } + @Test void finaALl_조회된_모든_기수를_DTO로_매핑처리() { From f040c297cd7bc9b4174c5a17e9d674a17d6a1c5b Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sat, 18 Oct 2025 20:55:54 +0900 Subject: [PATCH 012/114] =?UTF-8?q?test=20:=20CardinalUseCase=20-=20findAl?= =?UTF-8?q?l()=20=EC=A1=B0=ED=9A=8C=20.=20=EB=A7=A4=ED=95=91=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../usecase/CardinalUseCaseTest.java | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java index a5422b23..6b4fa81e 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java @@ -1,8 +1,10 @@ package leets.weeth.domain.user.application.usecase; import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.assertj.core.api.InstanceOfAssertFactories.*; import static org.mockito.BDDMockito.*; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -14,6 +16,7 @@ import leets.weeth.domain.user.application.dto.request.CardinalSaveRequest; import leets.weeth.domain.user.application.dto.request.CardinalUpdateRequest; +import leets.weeth.domain.user.application.dto.response.CardinalResponse; import leets.weeth.domain.user.application.mapper.CardinalMapper; import leets.weeth.domain.user.domain.entity.Cardinal; import leets.weeth.domain.user.domain.entity.enums.CardinalStatus; @@ -131,12 +134,67 @@ public class CardinalUseCaseTest { @Test - void finaALl_조회된_모든_기수를_DTO로_매핑처리() { + void findAll_조회된_모든_기수를_DTO로_매핑처리() { - } + //given + var cardinal1 = Cardinal.builder() + .id(1L) + .cardinalNumber(6) + .year(2024) + .semester(2) + .status(CardinalStatus.DONE) + .build(); + var cardinal2 = Cardinal.builder() + .id(2L) + .cardinalNumber(7) + .year(2025) + .semester(1) + .status(CardinalStatus.IN_PROGRESS) + .build(); + var cardinals = List.of(cardinal1, cardinal2); + var now = LocalDateTime.now(); + var response1 = new CardinalResponse( + 1L, 6, 2024, 2, + CardinalStatus.DONE, + now.minusDays(5), + now.minusDays(3) + ); + + var response2 = new CardinalResponse( + 2L, 7, 2025, 1, + CardinalStatus.IN_PROGRESS, + now.minusDays(2), + now + ); + + given(cardinalGetService.findAll()).willReturn(cardinals); + given(cardinalMapper.to(cardinal1)).willReturn(response1); + given(cardinalMapper.to(cardinal2)).willReturn(response2); + + //when + List responses = useCase.findAll(); + + + //then + then(cardinalGetService).should().findAll(); + then(cardinalMapper).should(times(2)).to(any(Cardinal.class)); + + assertThat(responses) + .asInstanceOf(list(CardinalResponse.class)) + .hasSize(2) + .extracting(CardinalResponse::cardinalNumber) + .containsExactly(6, 7); + + assertThat(responses) + .asInstanceOf(list(CardinalResponse.class)) + .extracting(CardinalResponse::status) + .containsExactly(CardinalStatus.DONE, CardinalStatus.IN_PROGRESS); + + assertThat(responses.get(0).createdAt()).isBefore(responses.get(1).createdAt()); + } } From 3dcd41e74845bbb3ecd3ddee3ca6bccb8759a652 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 19 Oct 2025 22:36:06 +0900 Subject: [PATCH 013/114] =?UTF-8?q?test=20:=20UserManageUseCaseImpl=5Ffind?= =?UTF-8?q?AllByAdmin=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/UserManageUseCaseTest.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java new file mode 100644 index 00000000..2295116c --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -0,0 +1,139 @@ +package leets.weeth.domain.user.application.usecase; + + +import static org.assertj.core.api.Assertions.*; + + +import java.time.LocalDateTime; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.BDDMockito.given; + +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.crypto.password.PasswordEncoder; + +import leets.weeth.domain.attendance.domain.service.AttendanceSaveService; +import leets.weeth.domain.schedule.domain.service.MeetingGetService; +import leets.weeth.domain.user.application.dto.response.UserResponseDto; +import leets.weeth.domain.user.application.exception.InvalidUserOrderException; +import leets.weeth.domain.user.application.mapper.UserMapper; +import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.UserCardinal; +import leets.weeth.domain.user.domain.entity.enums.Status; +import leets.weeth.domain.user.domain.entity.enums.UsersOrderBy; +import leets.weeth.domain.user.domain.service.CardinalGetService; +import leets.weeth.domain.user.domain.service.UserCardinalGetService; +import leets.weeth.domain.user.domain.service.UserCardinalSaveService; +import leets.weeth.domain.user.domain.service.UserDeleteService; +import leets.weeth.domain.user.domain.service.UserGetService; +import leets.weeth.domain.user.domain.service.UserUpdateService; +import leets.weeth.global.auth.jwt.service.JwtRedisService; + +@ExtendWith(MockitoExtension.class) +public class UserManageUseCaseTest { + + @Mock private UserGetService userGetService; + @Mock private UserUpdateService userUpdateService; + @Mock private UserDeleteService userDeleteService; + + @Mock private AttendanceSaveService attendanceSaveService; + @Mock private MeetingGetService meetingGetService; + @Mock private JwtRedisService jwtRedisService; + @Mock private CardinalGetService cardinalGetService; + @Mock private UserCardinalSaveService userCardinalSaveService; + @Mock private UserCardinalGetService userCardinalGetService; + + @Mock private UserMapper userMapper; + @Mock private PasswordEncoder passwordEncoder; + + @InjectMocks + private UserManageUseCaseImpl useCase; + + + @Test + void findAllByAdmin_orderBy가_null이면_예외가정상발생하는지(){ + //given + UsersOrderBy orderBy = null; + + //when & then + assertThatThrownBy(() -> useCase.findAllByAdmin(orderBy)) + .isInstanceOf(InvalidUserOrderException.class); + + } + + @Test + void findAllByAdmin이_orderBy에_맞게_정렬되어_조회되는지() { + //given + UsersOrderBy orderBy = UsersOrderBy.NAME_ASCENDING; + + var user1 = User.builder() + .id(1L) + .name("aaa") + .status(Status.ACTIVE) + .build(); + + var user2 = User.builder() + .id(2L) + .name("bbb") + .status(Status.WAITING) + .build(); + + var cd1 = Cardinal.builder() + .id(1L) + .cardinalNumber(6) + .year(2020) + .semester(2) + .build(); + + var cd2 = Cardinal.builder() + .id(2L) + .cardinalNumber(7) + .year(2021) + .semester(1) + .build(); + + var uc1 = new UserCardinal(user1, cd1); + var uc2 = new UserCardinal(user2, cd2); + + var adminResponse1 = new UserResponseDto.AdminResponse( + 1, "aaa", "a@a.com", "202034420", "01011112222", "산업공학과", + List.of(6), null, Status.ACTIVE, null, + 0, 0, 0, 0, 0, + LocalDateTime.now().minusDays(3), + LocalDateTime.now() + ); + + var adminResponse2 = new UserResponseDto.AdminResponse( + 2, "bbb", "b@b.com", "202045678", "01033334444", "컴퓨터공학과", + List.of(7), null, Status.WAITING, null, + 0, 0, 0, 0, 0, + LocalDateTime.now().minusDays(2), + LocalDateTime.now() + ); + + + + given(userCardinalGetService.getUserCardinals(user1)).willReturn(List.of(uc1)); + given(userCardinalGetService.getUserCardinals(user2)).willReturn(List.of(uc2)); + given(userCardinalGetService.findAll()).willReturn(List.of(uc1, uc2)); + given(userMapper.toAdminResponse(user1, List.of(uc1))).willReturn(adminResponse1); + given(userMapper.toAdminResponse(user2, List.of(uc2))).willReturn(adminResponse2); + + + //when + var result = useCase.findAllByAdmin(UsersOrderBy.NAME_ASCENDING); + + + //then + assertThat(result).hasSize(2); + assertThat(result.get(0).name()).isEqualTo("aaa"); + assertThat(result.get(1).name()).isEqualTo("bbb"); + + } + +} From c2fbff4bef060dba98ccc191a87b6e9ec145491b Mon Sep 17 00:00:00 2001 From: hyxklee Date: Sun, 19 Oct 2025 23:06:00 +0900 Subject: [PATCH 014/114] =?UTF-8?q?config:=20TestContainer=20=EB=B0=8F=20t?= =?UTF-8?q?est=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++++ .../weeth/config/TestContainersConfig.java | 19 ++++++++++++++ .../weeth/config/TestContainersTest.java | 25 +++++++++++++++++++ src/test/resources/application-test.yml | 11 ++++++++ 4 files changed, 60 insertions(+) create mode 100644 src/test/java/leets/weeth/config/TestContainersConfig.java create mode 100644 src/test/java/leets/weeth/config/TestContainersTest.java create mode 100644 src/test/resources/application-test.yml diff --git a/build.gradle b/build.gradle index dd370c44..02283446 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,11 @@ dependencies { // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "org.junit.jupiter:junit-jupiter:5.8.1" + testImplementation "org.testcontainers:testcontainers:2.0.1" + testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.1" + testImplementation "org.testcontainers:testcontainers-mysql:2.0.1" + testImplementation 'org.springframework.boot:spring-boot-testcontainers' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // Swagger diff --git a/src/test/java/leets/weeth/config/TestContainersConfig.java b/src/test/java/leets/weeth/config/TestContainersConfig.java new file mode 100644 index 00000000..017c3895 --- /dev/null +++ b/src/test/java/leets/weeth/config/TestContainersConfig.java @@ -0,0 +1,19 @@ +package leets.weeth.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.testcontainers.mysql.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +@TestConfiguration +public class TestContainersConfig { + private static final String MYSQL_IMAGE = "mysql:8.0.41"; + + @Bean + @ServiceConnection + public MySQLContainer mysqlContainer() { + return new MySQLContainer(DockerImageName.parse(MYSQL_IMAGE)) + .withReuse(true); + } +} diff --git a/src/test/java/leets/weeth/config/TestContainersTest.java b/src/test/java/leets/weeth/config/TestContainersTest.java new file mode 100644 index 00000000..131cce93 --- /dev/null +++ b/src/test/java/leets/weeth/config/TestContainersTest.java @@ -0,0 +1,25 @@ +package leets.weeth.config; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.testcontainers.mysql.MySQLContainer; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class TestContainersTest { + @Autowired + private MySQLContainer mysqlContainer; + + @Test + void 설정파일로_주입된_컨테이너_정상_동작_테스트() { + assertThat(mysqlContainer).isNotNull(); + assertThat(mysqlContainer.isRunning()).isTrue(); + System.out.println("Container JDBC URL: " + mysqlContainer.getJdbcUrl()); + } +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 00000000..4747e28c --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,11 @@ +spring: + profiles: + active: test + jpa: + hibernate: + ddl-auto: create-drop + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQL8Dialect From 4b0ab6d914e58f10a9f7ce14637545c3de6da642 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 19 Oct 2025 23:23:24 +0900 Subject: [PATCH 015/114] =?UTF-8?q?test=20:=20UserManageUseCaseImpl=5Facce?= =?UTF-8?q?pt=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20=EC=9C=A0=EC=A0=80?= =?UTF-8?q?=20=EC=8A=B9=EC=9D=B8=EC=8B=9C,=20=EB=82=B4=EB=B6=80=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/UserManageUseCaseTest.java | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index 2295116c..c5ef5dce 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.*; - +import static org.mockito.BDDMockito.*; import java.time.LocalDateTime; import java.util.List; @@ -11,13 +11,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import static org.mockito.BDDMockito.given; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.crypto.password.PasswordEncoder; import leets.weeth.domain.attendance.domain.service.AttendanceSaveService; +import leets.weeth.domain.schedule.domain.entity.Meeting; import leets.weeth.domain.schedule.domain.service.MeetingGetService; +import leets.weeth.domain.user.application.dto.request.UserRequestDto; import leets.weeth.domain.user.application.dto.response.UserResponseDto; import leets.weeth.domain.user.application.exception.InvalidUserOrderException; import leets.weeth.domain.user.application.mapper.UserMapper; @@ -136,4 +137,33 @@ public class UserManageUseCaseTest { } + + @Test + void accept_비활성유저_승인시_출석초기화_정상호출되는지() { + //given + var user1 = User.builder() + .id(1L) + .name("aaa") + .status(Status.WAITING) + .build(); + var userIds = new UserRequestDto.UserId(List.of(1L)); + var cardinal = Cardinal.builder() + .id(1L) + .cardinalNumber(8) + .build(); + var meetings = List.of(mock(Meeting.class)); + + given(userGetService.findAll(userIds.userId())).willReturn(List.of(user1)); + given(userCardinalGetService.getCurrentCardinal(user1)).willReturn(cardinal); + given(meetingGetService.find(8)).willReturn(meetings); + + //when + useCase.accept(userIds); + + //then + then(userUpdateService).should().accept(user1); + then(attendanceSaveService).should().init(user1,meetings); + + } + } From c6e1ce45631b5c136ad269912108d1f62f5bc460 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 20 Oct 2025 22:05:03 +0900 Subject: [PATCH 016/114] =?UTF-8?q?test=20:=20UserManageUseCase=5Fupdate?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=20ROLE=20=EB=B3=80=EA=B2=BD=20=EC=8B=9C?= =?UTF-8?q?=20DB,=20Redis=20=EB=AA=A8=EB=91=90=20=EA=B0=B1=EC=8B=A0?= =?UTF-8?q?=EB=90=98=EB=8A=94=EC=A7=80=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 --- .../usecase/UserManageUseCaseTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index c5ef5dce..87ecb6ce 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -6,6 +6,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,6 +26,7 @@ import leets.weeth.domain.user.domain.entity.Cardinal; import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.entity.UserCardinal; +import leets.weeth.domain.user.domain.entity.enums.Role; import leets.weeth.domain.user.domain.entity.enums.Status; import leets.weeth.domain.user.domain.entity.enums.UsersOrderBy; import leets.weeth.domain.user.domain.service.CardinalGetService; @@ -166,4 +168,20 @@ public class UserManageUseCaseTest { } + @Test + void update_유저권한변경시_DB와_Redis_모두갱신되는지() { + // given + var user1 = User.builder().id(1L).build(); + var request = new UserRequestDto.UserRoleUpdate(1L, Role.ADMIN); + + lenient().when(userGetService.find((Long)1L)).thenReturn(user1); + + // when + useCase.update(List.of(request)); + + // then + then(userUpdateService).should().update(user1, "ADMIN"); + then(jwtRedisService).should().updateRole(1L, "ADMIN"); + } + } From cfc02bff5b39de47a7fb54b5b9b0c276698cb7e9 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 20 Oct 2025 22:12:17 +0900 Subject: [PATCH 017/114] =?UTF-8?q?test=20:=20UserManageUseCase=5Fleave=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=ED=83=88=ED=87=B4=20=EC=8B=9C,=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EB=AC=B4=ED=9A=A8=ED=99=94=20=EB=B0=8F=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=B3=80=EA=B2=BD=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 --- .../usecase/UserManageUseCaseTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index 87ecb6ce..da0578ae 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -184,4 +184,23 @@ public class UserManageUseCaseTest { then(jwtRedisService).should().updateRole(1L, "ADMIN"); } + @Test + void leave_회원탈퇴시_토큰무효화_및_유저상태변경되는지() { + //given + var user1 = User.builder(). + id(1L) + .status(Status.ACTIVE) + .build(); + given(userGetService.find((Long)1L)).willReturn(user1); + + //when + useCase.leave(1L); + + //then + then(jwtRedisService).should().delete(1L); + then(userDeleteService).should().leave(user1); + + } + + } From bd614058b3208e7ec69876843c45ff3e4d0186f2 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 20 Oct 2025 22:23:23 +0900 Subject: [PATCH 018/114] =?UTF-8?q?test=20:=20UserManageUseCase=5Fban=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20ban=EC=8B=9C,=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=AC=B4=ED=9A=A8=ED=99=94=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/UserManageUseCaseTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index da0578ae..b95c4d23 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -202,5 +202,23 @@ public class UserManageUseCaseTest { } + @Test + void ban_회원ban시_토큰무효화_및_유저상태변경되는지() { + //given + var user1 = User.builder(). + id(1L) + .status(Status.ACTIVE) + .build(); + + var ids = new UserRequestDto.UserId(List.of(1L)); + given(userGetService.findAll(ids.userId())).willReturn(List.of(user1)); + //when + useCase.ban(ids); + + //then + then(jwtRedisService).should().delete(1L); + then(userDeleteService).should().ban(user1); + + } } From 1eb61f8d4ad14e0879a52703f087faea29b3510f Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 20 Oct 2025 22:59:30 +0900 Subject: [PATCH 019/114] =?UTF-8?q?test=20:=20UserManageUseCase=5FapplyOB?= =?UTF-8?q?=20=ED=98=84=EC=9E=AC=EA=B8=B0=EC=88=98=20OB=EC=8B=A0=EC=B2=AD?= =?UTF-8?q?=20=EC=8B=9C,=20=EC=B6=9C=EC=84=9D=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=EA=B8=B0=EC=88=98=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=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 --- .../usecase/UserManageUseCaseTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index b95c4d23..dad9cf92 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -5,6 +5,7 @@ import static org.mockito.BDDMockito.*; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -221,4 +222,35 @@ public class UserManageUseCaseTest { then(userDeleteService).should().ban(user1); } + + @Test + void applyOB_현재기수_OB신청시_출석초기화_및_기수업데이트() { + //given + var user = User.builder() + .id(1L) + .status(Status.ACTIVE) + .attendances(new ArrayList<>()) + .build(); + + var nextCardinal = Cardinal.builder() + .id(1L) + .cardinalNumber(4) + .build(); + + var request = new UserRequestDto.UserApplyOB(1L,4); + var meeting = List.of(mock(Meeting.class)); + + given(userGetService.find((Long)1L)).willReturn(user); + given(cardinalGetService.findByAdminSide(4)).willReturn(nextCardinal); + given(userCardinalGetService.notContains(user, nextCardinal)).willReturn(true); + given(userCardinalGetService.isCurrent(user, nextCardinal)).willReturn(true); + given(meetingGetService.find(4)).willReturn(meeting); + + //when + useCase.applyOB(List.of(request)); + + //then + then(attendanceSaveService).should().init(user,meeting); + then(userCardinalSaveService).should().save(any(UserCardinal.class)); + } } From 2f393b0682c54fd059a6a84a9265bb025f3e13bb Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Mon, 20 Oct 2025 23:07:10 +0900 Subject: [PATCH 020/114] =?UTF-8?q?test=20:=20UserManageUseCase=5Freset=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EC=8B=9C,=20reset=20=ED=98=B8=EC=B6=9C=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=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 --- .../usecase/UserManageUseCaseTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index dad9cf92..f40f7976 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -253,4 +253,33 @@ public class UserManageUseCaseTest { then(attendanceSaveService).should().init(user,meeting); then(userCardinalSaveService).should().save(any(UserCardinal.class)); } + + @Test + void reset_비밀번호초기화시_모든유저에_reset호출되는지() { + // given + var user1 = User.builder() + .id(1L) + .name("aaa") + .status(Status.ACTIVE) + .build(); + + var user2 = User.builder() + .id(2L) + .name("bbb") + .status(Status.ACTIVE) + .build(); + + var ids = new UserRequestDto.UserId(List.of(1L, 2L)); + + given(userGetService.findAll(ids.userId())).willReturn(List.of(user1, user2)); + + // when + useCase.reset(ids); + + // then + then(userGetService).should().findAll(ids.userId()); + then(userUpdateService).should().reset(user1, passwordEncoder); + then(userUpdateService).should().reset(user2, passwordEncoder); + } + } From e7bcba7a90a6b1d6c7e4ce815c9a7f87d4324465 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 22 Oct 2025 15:12:49 +0900 Subject: [PATCH 021/114] =?UTF-8?q?chore=20:=20TestContainers=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++++ .../weeth/config/TestContainersConfig.java | 19 ++++++++++++++ .../weeth/config/TestContainersTest.java | 25 +++++++++++++++++++ src/test/resources/application-test.yml | 12 +++++++++ 4 files changed, 61 insertions(+) create mode 100644 src/test/java/leets/weeth/config/TestContainersConfig.java create mode 100644 src/test/java/leets/weeth/config/TestContainersTest.java create mode 100644 src/test/resources/application-test.yml diff --git a/build.gradle b/build.gradle index dd370c44..02283446 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,11 @@ dependencies { // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "org.junit.jupiter:junit-jupiter:5.8.1" + testImplementation "org.testcontainers:testcontainers:2.0.1" + testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.1" + testImplementation "org.testcontainers:testcontainers-mysql:2.0.1" + testImplementation 'org.springframework.boot:spring-boot-testcontainers' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // Swagger diff --git a/src/test/java/leets/weeth/config/TestContainersConfig.java b/src/test/java/leets/weeth/config/TestContainersConfig.java new file mode 100644 index 00000000..4174b062 --- /dev/null +++ b/src/test/java/leets/weeth/config/TestContainersConfig.java @@ -0,0 +1,19 @@ +package leets.weeth.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.testcontainers.mysql.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +@TestConfiguration +public class TestContainersConfig { + private static final String MYSQL_IMAGE = "mysql:8.0.41"; + + @Bean + @ServiceConnection + public MySQLContainer mysqlContainer() { + return new MySQLContainer(DockerImageName.parse(MYSQL_IMAGE)) + .withReuse(true); + } +} diff --git a/src/test/java/leets/weeth/config/TestContainersTest.java b/src/test/java/leets/weeth/config/TestContainersTest.java new file mode 100644 index 00000000..cf6f94c0 --- /dev/null +++ b/src/test/java/leets/weeth/config/TestContainersTest.java @@ -0,0 +1,25 @@ +package leets.weeth.config; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.testcontainers.mysql.MySQLContainer; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class TestContainersTest { + @Autowired + private MySQLContainer mysqlContainer; + + @Test + void 설정파일로_주입된_컨테이너_정상_동작_테스트() { + assertThat(mysqlContainer).isNotNull(); + assertThat(mysqlContainer.isRunning()).isTrue(); + System.out.println("Container JDBC URL: " + mysqlContainer.getJdbcUrl()); + } +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 00000000..c20d267f --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,12 @@ +spring: + profiles: + active: test + jpa: + hibernate: + ddl-auto: create-drop + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQL8Dialect + From f6394e3c26c835b438718bb2808cfae7d8b62240 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 22 Oct 2025 15:50:22 +0900 Subject: [PATCH 022/114] =?UTF-8?q?test=20:=20CardinalRepository=20?= =?UTF-8?q?=EA=B0=92=20=EC=A3=BC=EC=9E=85=20=EB=B0=8F=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CardinalRepositoryTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java new file mode 100644 index 00000000..c4307406 --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java @@ -0,0 +1,42 @@ +package leets.weeth.domain.user.domain.repository; + +import static org.assertj.core.api.AssertionsForClassTypes.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import leets.weeth.config.TestContainersConfig; +import leets.weeth.domain.user.domain.entity.Cardinal; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class CardinalRepositoryTest { + + @Autowired + CardinalRepository cardinalRepository; + + @Test + void 기수번호로_조회되는지() { + //given + Cardinal cardinal = Cardinal.builder() + .id(1L) + .cardinalNumber(7) + .year(2025) + .semester(1) + .build(); + cardinalRepository.save(cardinal); + + //when + var result = cardinalRepository.findByCardinalNumber(7); + + //then + assertThat(result).isPresent(); + assertThat(result.get().getYear()).isEqualTo(2025); + } + + +} From 44629fb39148da56305d75f8187ef6f7679f05f0 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 22 Oct 2025 16:33:36 +0900 Subject: [PATCH 023/114] =?UTF-8?q?teat=20:=20UserCardinalRepositoryTest?= =?UTF-8?q?=20=EA=B8=B0=EC=88=98=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UsesrCardinalRepositoryTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java new file mode 100644 index 00000000..a1c715ad --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java @@ -0,0 +1,82 @@ +package leets.weeth.domain.user.domain.repository; + +import static org.assertj.core.api.Assertions.assertThat; + + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import leets.weeth.config.TestContainersConfig; +import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.UserCardinal; +import leets.weeth.domain.user.domain.entity.enums.Status; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class UsesrCardinalRepositoryTest { + + @Autowired + UserRepository userRepository; + + @Autowired + CardinalRepository cardinalRepository; + + @Autowired + UserCardinalRepository userCardinalRepository; + + @Test + void 유저별_기수_내림차순_조회되는지() { + //given + User user = User.builder() + .email("test@test.com") + .name("문적순") + .status(Status.ACTIVE) + .build(); + + userRepository.save(user); + + Cardinal cardinal1 = cardinalRepository.save(Cardinal.builder() + .cardinalNumber(5) + .year(2023) + .semester(1) + .build()); + + Cardinal cardinal2 = cardinalRepository.save(Cardinal.builder() + .cardinalNumber(6) + .year(2023) + .semester(2) + .build()); + + Cardinal cardinal3 = cardinalRepository.save(Cardinal.builder() + .cardinalNumber(7) + .year(2024) + .semester(1) + .build()); + + + userCardinalRepository.saveAll(List.of( + new UserCardinal(user, cardinal1), + new UserCardinal(user, cardinal2), + new UserCardinal(user, cardinal3) + )); + + //when + List result = userCardinalRepository.findAllByUserOrderByCardinalCardinalNumberDesc(user); + + //then + assertThat(result).hasSize(3); + assertThat(result.get(0).getCardinal().getCardinalNumber()).isEqualTo(7); + assertThat(result.get(1).getCardinal().getCardinalNumber()).isEqualTo(6); + assertThat(result.get(2).getCardinal().getCardinalNumber()).isEqualTo(5); + + } + + +} From 500805d084ac25a732728c3225589468a79aff6b Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 22 Oct 2025 17:05:05 +0900 Subject: [PATCH 024/114] =?UTF-8?q?test=20:=20UserCardinalRepository=20?= =?UTF-8?q?=EC=97=AC=EB=9F=AC=20=EC=9C=A0=EC=A0=80=EC=9D=98=20=EA=B8=B0?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EC=9C=A0=EC=A0=80=EB=B3=84=EB=A1=9C=20?= =?UTF-8?q?=EB=AC=B6=EC=96=B4=EC=84=9C=20=EB=82=B4=EB=A6=BC=EC=B0=A8?= =?UTF-8?q?=EC=88=9C=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=ED=9A=8C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UsesrCardinalRepositoryTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java index a1c715ad..08f66d97 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java @@ -78,5 +78,39 @@ public class UsesrCardinalRepositoryTest { } + @Test + void 여러_유저의_기수를_유저별_내림차순으로_조회한다() { + //given + User user1 = userRepository.save(User.builder().email("user1@test.com").name("적순").status(Status.ACTIVE).build()); + User user2 = userRepository.save(User.builder().email("user2@test.com").name("순적").status(Status.ACTIVE).build()); + + Cardinal c1 = cardinalRepository.save(Cardinal.builder().cardinalNumber(5).year(2023).semester(1).build()); + Cardinal c2 = cardinalRepository.save(Cardinal.builder().cardinalNumber(6).year(2023).semester(2).build()); + Cardinal c3 = cardinalRepository.save(Cardinal.builder().cardinalNumber(7).year(2024).semester(1).build()); + Cardinal c4 = cardinalRepository.save(Cardinal.builder().cardinalNumber(8).year(2024).semester(2).build()); + + userCardinalRepository.saveAll(List.of( + new UserCardinal(user1, c3), + new UserCardinal(user1, c2) + )); + userCardinalRepository.saveAll(List.of( + new UserCardinal(user2, c4), + new UserCardinal(user2, c1) + )); + + //when + List result = userCardinalRepository.findAllByUsers(List.of(user1, user2)); + + //then + assertThat(result).hasSize(4); + assertThat(result.get(0).getUser().getId()).isEqualTo(user1.getId()); + assertThat(result.get(0).getCardinal().getCardinalNumber()).isEqualTo(7); + assertThat(result.get(1).getCardinal().getCardinalNumber()).isEqualTo(6); + + assertThat(result.get(2).getUser().getId()).isEqualTo(user2.getId()); + assertThat(result.get(2).getCardinal().getCardinalNumber()).isEqualTo(8); + assertThat(result.get(3).getCardinal().getCardinalNumber()).isEqualTo(5); + } + } From 99ff375b2a4568a9d2ec9493496bff8bb3b8757f Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 22 Oct 2025 19:58:07 +0900 Subject: [PATCH 025/114] =?UTF-8?q?test:=20notice=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../domain/service/NoticeSaveServiceTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java diff --git a/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java b/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java new file mode 100644 index 00000000..6e016b22 --- /dev/null +++ b/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java @@ -0,0 +1,57 @@ +package leets.weeth.domain.board.domain.service; + +import leets.weeth.config.TestContainersConfig; +import leets.weeth.domain.board.domain.entity.Notice; +import leets.weeth.domain.board.domain.repository.NoticeRepository; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Department; +import leets.weeth.domain.user.domain.entity.enums.Position; +import leets.weeth.domain.user.domain.entity.enums.Role; +import leets.weeth.domain.user.domain.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class NoticeSaveServiceTest { + + @Autowired + UserRepository userRepository; + @Autowired + NoticeRepository noticeRepository; + + + + @Test + void save() { + // given + User user = userRepository.save(User.builder() + .email("abc@test.com") + .name("홍길동") + .position(Position.BE) + .department(Department.SW) + .role(Role.USER) + .build()); + + Notice notice = Notice.builder() + .title("제목") + .content("내용") + .user(user) + .build(); + + // when + Notice savedNotice = noticeRepository.save(notice); + + // then + assertThat(noticeRepository.findAll()).hasSize(1); + assertThat(savedNotice.getTitle()).isEqualTo(notice.getTitle()); + assertThat(savedNotice.getContent()).isEqualTo(notice.getContent()); + assertThat(savedNotice.getUser().getId()).isEqualTo(user.getId()); + } +} \ No newline at end of file From c510657bb17a7364e525ae34d949d84df1c5849e Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 22 Oct 2025 20:42:41 +0900 Subject: [PATCH 026/114] =?UTF-8?q?test:=20notice=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../repository/NoticeRepositoryTest.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java diff --git a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java new file mode 100644 index 00000000..1b152b6b --- /dev/null +++ b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java @@ -0,0 +1,91 @@ +package leets.weeth.domain.board.domain.repository; + +import leets.weeth.config.TestContainersConfig; +import leets.weeth.domain.board.domain.entity.Notice; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Department; +import leets.weeth.domain.user.domain.entity.enums.Position; +import leets.weeth.domain.user.domain.entity.enums.Role; +import leets.weeth.domain.user.domain.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class NoticeRepositoryTest { + + @Autowired + UserRepository userRepository; + + @Autowired + NoticeRepository noticeRepository; + + @Test + void findPageBy() { + // given + User user = userRepository.save(User.builder() + .email("abc@test.com") + .name("홍길동") + .position(Position.BE) + .department(Department.SW) + .role(Role.USER) + .build()); + + Notice notice1 = noticeRepository.save(Notice.builder() + .title("제목1") + .content("내용1") + .user(user) + .build()); + + Notice notice2 = noticeRepository.save(Notice.builder() + .title("제목2") + .content("내용2") + .user(user) + .build()); + + Notice notice3 = noticeRepository.save(Notice.builder() + .title("제목3") + .content("내용3") + .user(user) + .build()); + + Notice notice4 = noticeRepository.save(Notice.builder() + .title("제목4") + .content("내용4") + .user(user) + .build()); + + Notice notice5 = noticeRepository.save(Notice.builder() + .title("제목5") + .content("내용5") + .user(user) + .build()); + + Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "id")); + + // when + Slice notices = noticeRepository.findPageBy(pageable); + + // then + assertThat(notices.getSize()).isEqualTo(3); + assertThat(notices.getContent().get(0).getTitle()).isEqualTo(notice5.getTitle()); + assertThat(notices.getContent().get(1).getTitle()).isEqualTo(notice4.getTitle()); + assertThat(notices.getContent().get(2).getTitle()).isEqualTo(notice3.getTitle()); + assertThat(notices.hasNext()).isEqualTo(true); + } + + @Test + void search() { + } +} \ No newline at end of file From 4041f1e2b9eb70df18952cbd04dd821a0f0fd238 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 22 Oct 2025 20:47:23 +0900 Subject: [PATCH 027/114] =?UTF-8?q?test:=20notice=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../repository/NoticeRepositoryTest.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java index 1b152b6b..8be2bc8a 100644 --- a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java @@ -87,5 +87,54 @@ void findPageBy() { @Test void search() { + // given + User user = userRepository.save(User.builder() + .email("abc@test.com") + .name("홍길동") + .position(Position.BE) + .department(Department.SW) + .role(Role.USER) + .build()); + + Notice notice1 = noticeRepository.save(Notice.builder() + .title("검색1") + .content("내용1") + .user(user) + .build()); + + Notice notice2 = noticeRepository.save(Notice.builder() + .title("제목2") + .content("내용2") + .user(user) + .build()); + + Notice notice3 = noticeRepository.save(Notice.builder() + .title("검색3") + .content("내용3") + .user(user) + .build()); + + Notice notice4 = noticeRepository.save(Notice.builder() + .title("제목4") + .content("내용4") + .user(user) + .build()); + + Notice notice5 = noticeRepository.save(Notice.builder() + .title("검색5") + .content("내용5") + .user(user) + .build()); + + Pageable pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "id")); + + // when + Slice searchedNotices = noticeRepository.search("검색", pageable); + + // then + assertThat(searchedNotices.getSize()).isEqualTo(2); + assertThat(searchedNotices.getContent().get(0).getTitle()).isEqualTo(notice5.getTitle()); + assertThat(searchedNotices.getContent().get(1).getTitle()).isEqualTo(notice3.getTitle()); + assertThat(searchedNotices.hasNext()).isEqualTo(true); } } \ No newline at end of file From e666d1e137574f6a3fccf2dbe359a5ecaf46fe8d Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 24 Oct 2025 20:24:15 +0900 Subject: [PATCH 028/114] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/domain/repository/NoticeRepositoryTest.java | 2 +- .../domain/board/domain/service/NoticeSaveServiceTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java index 8be2bc8a..75c612f0 100644 --- a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java @@ -137,4 +137,4 @@ void search() { assertThat(searchedNotices.getContent().get(1).getTitle()).isEqualTo(notice3.getTitle()); assertThat(searchedNotices.hasNext()).isEqualTo(true); } -} \ No newline at end of file +} diff --git a/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java b/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java index 6e016b22..c9dc031d 100644 --- a/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java @@ -54,4 +54,4 @@ void save() { assertThat(savedNotice.getContent()).isEqualTo(notice.getContent()); assertThat(savedNotice.getUser().getId()).isEqualTo(user.getId()); } -} \ No newline at end of file +} From ac94ba83b95fd0986e1a0e9c4a803028334f88b7 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 24 Oct 2025 20:25:37 +0900 Subject: [PATCH 029/114] =?UTF-8?q?test:=20NoticeSaveService=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(@DataJpaTest=20+=20Service=20=ED=8F=AC=ED=95=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/domain/service/NoticeSaveServiceTest.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java b/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java index c9dc031d..1171c34d 100644 --- a/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java @@ -14,10 +14,12 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; +import java.util.List; + import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; @DataJpaTest -@Import(TestContainersConfig.class) +@Import({TestContainersConfig.class, NoticeSaveService.class}) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class NoticeSaveServiceTest { @@ -26,7 +28,8 @@ class NoticeSaveServiceTest { @Autowired NoticeRepository noticeRepository; - + @Autowired + NoticeSaveService noticeSaveService; @Test void save() { @@ -46,10 +49,12 @@ void save() { .build(); // when - Notice savedNotice = noticeRepository.save(notice); + noticeSaveService.save(notice); // then - assertThat(noticeRepository.findAll()).hasSize(1); + List notices = noticeRepository.findAll(); + assertThat(notices).hasSize(1); + Notice savedNotice = notices.get(0); assertThat(savedNotice.getTitle()).isEqualTo(notice.getTitle()); assertThat(savedNotice.getContent()).isEqualTo(notice.getContent()); assertThat(savedNotice.getUser().getId()).isEqualTo(user.getId()); From 31894d4e42b177880df4a0890c9370942053d6f3 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 25 Oct 2025 15:15:04 +0900 Subject: [PATCH 030/114] =?UTF-8?q?test:=20Notice=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20NoticeFixture=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/domain/fixture/NoticeFixture.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/board/domain/fixture/NoticeFixture.java diff --git a/src/test/java/leets/weeth/domain/board/domain/fixture/NoticeFixture.java b/src/test/java/leets/weeth/domain/board/domain/fixture/NoticeFixture.java new file mode 100644 index 00000000..59356932 --- /dev/null +++ b/src/test/java/leets/weeth/domain/board/domain/fixture/NoticeFixture.java @@ -0,0 +1,15 @@ +package leets.weeth.domain.board.domain.fixture; + +import leets.weeth.domain.board.domain.entity.Notice; +import leets.weeth.domain.user.domain.entity.User; + +public class NoticeFixture { + public static Notice createNotice(String title, User user){ + return Notice.builder() + .title(title) + .content("내용") + .user(user) + .commentCount(0) + .build(); + } +} From ee464cf077515c68b0b5efe870bfa9f612856afe Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 25 Oct 2025 15:15:57 +0900 Subject: [PATCH 031/114] =?UTF-8?q?test:=20Notice=20=EA=B3=B5=EC=A7=80?= =?UTF-8?q?=EC=82=AC=ED=95=AD=EC=9D=B4=20=EC=B5=9C=EC=8B=A0=EC=88=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20=ED=99=95=EC=9D=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/NoticeUsecaseImplTest.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java new file mode 100644 index 00000000..8c498a2e --- /dev/null +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -0,0 +1,105 @@ +package leets.weeth.domain.board.application.usecase; + +import leets.weeth.domain.board.application.dto.NoticeDTO; +import leets.weeth.domain.board.application.mapper.NoticeMapper; +import leets.weeth.domain.board.domain.entity.Notice; +import leets.weeth.domain.board.domain.fixture.NoticeFixture; +import leets.weeth.domain.board.domain.service.NoticeFindService; +import leets.weeth.domain.file.domain.service.FileGetService; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Department; +import leets.weeth.domain.user.domain.entity.enums.Position; +import leets.weeth.domain.user.domain.entity.enums.Role; +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.data.domain.*; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class NoticeUsecaseImplTest { + + @Mock + private NoticeFindService noticeFindService; + @Mock + private FileGetService fileGetService; + + @InjectMocks + private NoticeUsecaseImpl noticeUsecase; + + @Mock + private NoticeMapper noticeMapper; + + @Test + void 공지사항이_최신순으로_정렬되는지() { + // given + User user = User.builder() + .email("abc@test.com") + .name("홍길동") + .position(Position.BE) + .department(Department.SW) + .role(Role.USER) + .build(); + + List notices = new ArrayList<>(); + for(int i = 0; i<5; i++){ + Notice notice = NoticeFixture.createNotice("공지" + i, user); + ReflectionTestUtils.setField(notice, "id", (long) i + 1); + notices.add(notice); + } + + Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "id")); + + Slice slice = new SliceImpl<>(List.of(notices.get(4), notices.get(3), notices.get(2)), pageable, true); + + when(noticeFindService.findRecentNotices(any(Pageable.class))).thenReturn(slice); + when(fileGetService.findAllByNotice(nullable(Long.class))).thenReturn(List.of()); + + when(noticeMapper.toAll(any(Notice.class), anyBoolean())) + .thenAnswer(invocation -> { + Notice notice = invocation.getArgument(0); + return new NoticeDTO.ResponseAll( + notice.getId(), + notice.getUser() != null ? notice.getUser().getName() : "", + notice.getUser() != null ? notice.getUser().getPosition() : Position.BE, + notice.getUser() != null ? notice.getUser().getRole() : Role.USER, + notice.getTitle(), + notice.getContent(), + notice.getCreatedAt(), + notice.getCommentCount(), + false + ); + }); + + // when + Slice noticeResponses = noticeUsecase.findNotices(0, 3); + + // then + assertThat(noticeResponses).isNotNull(); + assertThat(noticeResponses.getContent()).hasSize(3); + assertThat(noticeResponses.getContent().get(0).title()).isEqualTo(notices.get(4).getTitle()); + + verify(noticeFindService, times(1)).findRecentNotices(pageable); + + } + + @Test + void searchNotice() { + } + + @Test + void update() { + } + + @Test + void delete() { + } +} From fc4a295be41357d936cf1ebcdbee51f66bb4f66f Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 25 Oct 2025 16:54:58 +0900 Subject: [PATCH 032/114] =?UTF-8?q?test:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EA=B2=80=EC=83=89=EC=8B=9C=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EC=99=80=20=ED=8C=8C=EC=9D=BC=20=EC=A1=B4=EC=9E=AC=EC=97=AC?= =?UTF-8?q?=EB=B6=80=EA=B0=80=20=EC=A0=95=EC=83=81=EC=A0=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=EC=A7=80=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=ED=95=98=EB=8A=94=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/NoticeUsecaseImplTest.java | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java index 8c498a2e..6e283e9b 100644 --- a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -5,6 +5,7 @@ import leets.weeth.domain.board.domain.entity.Notice; import leets.weeth.domain.board.domain.fixture.NoticeFixture; import leets.weeth.domain.board.domain.service.NoticeFindService; +import leets.weeth.domain.file.domain.entity.File; import leets.weeth.domain.file.domain.service.FileGetService; import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.entity.enums.Department; @@ -92,7 +93,78 @@ class NoticeUsecaseImplTest { } @Test - void searchNotice() { + void 공지사항_검색시_결과와_파일_존재여부가_정상적으로_반환() { + // given + User user = User.builder() + .email("abc@test.com") + .name("홍길동") + .position(Position.BE) + .department(Department.SW) + .role(Role.USER) + .build(); + + List notices = new ArrayList<>(); + for(int i = 0; i<3; i++){ + Notice notice = NoticeFixture.createNotice("공지" + i, user); + ReflectionTestUtils.setField(notice, "id", (long) i + 1); + notices.add(notice); + } + for(int i = 3; i<6; i++){ + Notice notice = NoticeFixture.createNotice("검색" + i, user); + ReflectionTestUtils.setField(notice, "id", (long) i + 1); + notices.add(notice); + } + + + Pageable pageable = PageRequest.of(0, 5, Sort.by(Sort.Direction.DESC, "id")); + + Slice slice = new SliceImpl<>(List.of(notices.get(5), notices.get(4), notices.get(3)), pageable, false); + + when(noticeFindService.search(any(String.class), any(Pageable.class))).thenReturn(slice); + // 짝수 id - 파일 존재, 홀수 id - 파일 없음 (빈 리스트) + when(fileGetService.findAllByNotice(any())) + .thenAnswer(invocation -> { + Long noticeId = invocation.getArgument(0); + if (noticeId % 2 == 0) { + return List.of(File.builder() + .notice(notices.get((int)(noticeId-1))) + .build()); + } else { + return List.of(); + } + }); + + when(noticeMapper.toAll(any(Notice.class), anyBoolean())) + .thenAnswer(invocation -> { + Notice notice = invocation.getArgument(0); + boolean fileExists = invocation.getArgument(1); + return new NoticeDTO.ResponseAll( + notice.getId(), + notice.getUser() != null ? notice.getUser().getName() : "", + notice.getUser() != null ? notice.getUser().getPosition() : Position.BE, + notice.getUser() != null ? notice.getUser().getRole() : Role.USER, + notice.getTitle(), + notice.getContent(), + notice.getCreatedAt(), + notice.getCommentCount(), + fileExists + ); + }); + + // when + Slice noticeResponses = noticeUsecase.searchNotice("검색", 0, 5); + + // then + assertThat(noticeResponses).isNotNull(); + assertThat(noticeResponses.getContent()).hasSize(3); + assertThat(noticeResponses.getContent().get(0).title()).isEqualTo(notices.get(5).getTitle()); + assertThat(noticeResponses.hasNext()).isFalse(); + + // 짝수 id : 파일 존재, 홀수 id : 파일 없음 검증 + assertThat(noticeResponses.getContent().get(0).hasFile()).isTrue(); + assertThat(noticeResponses.getContent().get(1).hasFile()).isFalse(); + + verify(noticeFindService, times(1)).search("검색", pageable); } @Test From c9280de08f997f8066efd5aaf86e0280d624710f Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 25 Oct 2025 16:56:39 +0900 Subject: [PATCH 033/114] =?UTF-8?q?test:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=B5=9C=EC=8B=A0=EC=88=9C=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20fileGetService=20mock=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9D=84=20nullable(Long)=20=E2=86=92=20any()=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20hasNext()=20=EC=A0=95=EC=83=81=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=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 --- .../board/application/usecase/NoticeUsecaseImplTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java index 6e283e9b..bee8048c 100644 --- a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -62,7 +62,7 @@ class NoticeUsecaseImplTest { Slice slice = new SliceImpl<>(List.of(notices.get(4), notices.get(3), notices.get(2)), pageable, true); when(noticeFindService.findRecentNotices(any(Pageable.class))).thenReturn(slice); - when(fileGetService.findAllByNotice(nullable(Long.class))).thenReturn(List.of()); + when(fileGetService.findAllByNotice(any())).thenReturn(List.of()); when(noticeMapper.toAll(any(Notice.class), anyBoolean())) .thenAnswer(invocation -> { @@ -87,6 +87,7 @@ class NoticeUsecaseImplTest { assertThat(noticeResponses).isNotNull(); assertThat(noticeResponses.getContent()).hasSize(3); assertThat(noticeResponses.getContent().get(0).title()).isEqualTo(notices.get(4).getTitle()); + assertThat(noticeResponses.hasNext()).isTrue(); verify(noticeFindService, times(1)).findRecentNotices(pageable); From 5dba9bdab43924f7719d679b4166e79025c32774 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 25 Oct 2025 20:13:41 +0900 Subject: [PATCH 034/114] =?UTF-8?q?chore:=20/actuator/health=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/leets/weeth/global/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/leets/weeth/global/config/SecurityConfig.java b/src/main/java/leets/weeth/global/config/SecurityConfig.java index c1dcab2a..625337ee 100644 --- a/src/main/java/leets/weeth/global/config/SecurityConfig.java +++ b/src/main/java/leets/weeth/global/config/SecurityConfig.java @@ -80,7 +80,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/js/**", "/img/**", "/scss/**", "/vendor/**").permitAll() // 스웨거 경로 .requestMatchers("/v3/api-docs", "/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/swagger/**").permitAll() - .requestMatchers("/actuator/prometheus").permitAll() + .requestMatchers("/actuator/health", "actuator/prometheus").permitAll() .requestMatchers("/api/v1/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) From bd0aaaf60af9b474c12131cd5a11c58841226a2b Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 25 Oct 2025 20:56:53 +0900 Subject: [PATCH 035/114] =?UTF-8?q?feat:=20=EC=A0=91=EA=B7=BC=20=EB=84=A4?= =?UTF-8?q?=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EC=88=98=EC=A0=95=20-=20EC2=20?= =?UTF-8?q?=EB=82=B4=EB=B6=80=20=EB=98=90=EB=8A=94=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20=EB=82=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=EB=A7=8C=20/actuator/prometheus=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=20=ED=97=88=EC=9A=A9=20-=20/actuator/health=20=EC=97=94?= =?UTF-8?q?=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=99=B8=EB=B6=80=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/leets/weeth/global/config/SecurityConfig.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/leets/weeth/global/config/SecurityConfig.java b/src/main/java/leets/weeth/global/config/SecurityConfig.java index 625337ee..6f33c2a0 100644 --- a/src/main/java/leets/weeth/global/config/SecurityConfig.java +++ b/src/main/java/leets/weeth/global/config/SecurityConfig.java @@ -18,6 +18,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -80,7 +81,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/js/**", "/img/**", "/scss/**", "/vendor/**").permitAll() // 스웨거 경로 .requestMatchers("/v3/api-docs", "/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**", "/swagger/**").permitAll() - .requestMatchers("/actuator/health", "actuator/prometheus").permitAll() + .requestMatchers("/actuator/prometheus") + .access((authentication, context) -> { + String ip = context.getRequest().getRemoteAddr(); + boolean allowed = ip.startsWith("172.") || ip.equals("127.0.0.1"); + return new AuthorizationDecision(allowed); + }) + .requestMatchers("/actuator/health").permitAll() .requestMatchers("/api/v1/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) From 38692ee2b8191df348a29159b7071be4a63fac90 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 17:22:51 +0900 Subject: [PATCH 036/114] =?UTF-8?q?fix=20:=20TestContainer=20config=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/config/TestContainersConfig.java | 19 ++++++++++++++ .../weeth/config/TestContainersTest.java | 25 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/test/java/leets/weeth/config/TestContainersConfig.java create mode 100644 src/test/java/leets/weeth/config/TestContainersTest.java diff --git a/src/test/java/leets/weeth/config/TestContainersConfig.java b/src/test/java/leets/weeth/config/TestContainersConfig.java new file mode 100644 index 00000000..017c3895 --- /dev/null +++ b/src/test/java/leets/weeth/config/TestContainersConfig.java @@ -0,0 +1,19 @@ +package leets.weeth.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.testcontainers.mysql.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +@TestConfiguration +public class TestContainersConfig { + private static final String MYSQL_IMAGE = "mysql:8.0.41"; + + @Bean + @ServiceConnection + public MySQLContainer mysqlContainer() { + return new MySQLContainer(DockerImageName.parse(MYSQL_IMAGE)) + .withReuse(true); + } +} diff --git a/src/test/java/leets/weeth/config/TestContainersTest.java b/src/test/java/leets/weeth/config/TestContainersTest.java new file mode 100644 index 00000000..52f82570 --- /dev/null +++ b/src/test/java/leets/weeth/config/TestContainersTest.java @@ -0,0 +1,25 @@ +package leets.weeth.config; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; +import org.testcontainers.mysql.MySQLContainer; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class TestContainersTest { + @Autowired + private MySQLContainer mysqlContainer; + + @Test + void 설정파일로_주입된_컨테이너_정상_동작_테스트() { + assertThat(mysqlContainer).isNotNull(); + assertThat(mysqlContainer.isRunning()).isTrue(); + System.out.println("Container JDBC URL: " + mysqlContainer.getJdbcUrl()); + } +} From a9e8c025811f120c191422a1ec9f0abf72914190 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 17:23:21 +0900 Subject: [PATCH 037/114] =?UTF-8?q?test=20:=20Test=20=ED=99=98=EA=B2=BD=20?= =?UTF-8?q?yml=20=EB=B0=8F=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++++ src/test/resources/application-test.yml | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/test/resources/application-test.yml diff --git a/build.gradle b/build.gradle index dd370c44..02283446 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,11 @@ dependencies { // Test testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "org.junit.jupiter:junit-jupiter:5.8.1" + testImplementation "org.testcontainers:testcontainers:2.0.1" + testImplementation "org.testcontainers:testcontainers-junit-jupiter:2.0.1" + testImplementation "org.testcontainers:testcontainers-mysql:2.0.1" + testImplementation 'org.springframework.boot:spring-boot-testcontainers' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // Swagger diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 00000000..1ec8e8c2 --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,11 @@ +spring: + profiles: + active: test + jpa: + hibernate: + ddl-auto: create-drop + show-sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQL8Dialect \ No newline at end of file From 4bdba2339ae064a6f24cef627c5372a9c30d2d63 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 17:27:27 +0900 Subject: [PATCH 038/114] =?UTF-8?q?test=20:=20=ED=8A=B9=EC=A0=95=20?= =?UTF-8?q?=EC=A0=95=EA=B8=B0=EB=AA=A8=EC=9E=84=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=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 --- .../repository/AttendanceRepositoryTest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java diff --git a/src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java b/src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java new file mode 100644 index 00000000..55608158 --- /dev/null +++ b/src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java @@ -0,0 +1,80 @@ +package leets.weeth.domain.attendance.domain.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; +import java.util.List; + +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.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import leets.weeth.config.TestContainersConfig; +import leets.weeth.domain.attendance.domain.entity.Attendance; +import leets.weeth.domain.schedule.domain.entity.Meeting; +import leets.weeth.domain.schedule.domain.entity.enums.MeetingStatus; +import leets.weeth.domain.schedule.domain.repository.MeetingRepository; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Status; +import leets.weeth.domain.user.domain.repository.UserRepository; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class AttendanceRepositoryTest { + + @Autowired private AttendanceRepository attendanceRepository; + @Autowired private MeetingRepository meetingRepository; + @Autowired private UserRepository userRepository; + + private Meeting meeting; + private User activeUser1; + private User activeUser2; + + @BeforeEach + void setUp() { + meeting = Meeting.builder() + .title("1차 정기모임") + .start(LocalDateTime.now().minusHours(1)) + .end(LocalDateTime.now().plusHours(1)) + .code(1234) + .cardinal(1) + .meetingStatus(MeetingStatus.OPEN) + .build(); + meetingRepository.save(meeting); + + activeUser1 = User.builder().name("이지훈").status(Status.ACTIVE).build(); + activeUser2 = User.builder().name("이강혁").status(Status.ACTIVE).build(); + userRepository.saveAll(List.of(activeUser1, activeUser2)); + + attendanceRepository.save(new Attendance(meeting, activeUser1)); + attendanceRepository.save(new Attendance(meeting, activeUser2)); + } + + @Test + @DisplayName("특정 정기모임 + 사용자 상태로 출석 목록 조회") + void findAllByMeetingAndUserStatus() { + // when + List attendances = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE); + + // then + assertThat(attendances).hasSize(2); + assertThat(attendances).extracting(a -> a.getUser().getName()) + .containsExactlyInAnyOrder("이지훈", "이강혁"); + } + + @Test + @DisplayName("특정 정기모임의 모든 출석 레코드 삭제") + void deleteAllByMeeting() { + // when + attendanceRepository.deleteAllByMeeting(meeting); + + // then + List after = attendanceRepository.findAll(); + assertThat(after).isEmpty(); + } +} From 8a0a0aa91cac667f28c17466f17f0a32e14bf7be Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 17:36:15 +0900 Subject: [PATCH 039/114] =?UTF-8?q?test=20:=20AttendanceSave=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=EB=8B=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AttendanceSaveServiceTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java diff --git a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java new file mode 100644 index 00000000..4ea5d390 --- /dev/null +++ b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java @@ -0,0 +1,66 @@ +package leets.weeth.domain.attendance.domain.service; + +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import leets.weeth.domain.attendance.domain.entity.Attendance; +import leets.weeth.domain.attendance.domain.repository.AttendanceRepository; +import leets.weeth.domain.schedule.domain.entity.Meeting; +import leets.weeth.domain.user.domain.entity.User; + +@ExtendWith(MockitoExtension.class) +public class AttendanceSaveServiceTest { + + @Mock private AttendanceRepository attendanceRepository; + @InjectMocks private AttendanceSaveService attendanceSaveService; + + @Test + @DisplayName("init(user, meetings): 각 미팅에 대한 Attendance 저장 후 user.add 호출") + void init_createsAttendanceAndLinkToUser() { + // given + User user = mock(User.class); + Meeting m1 = mock(Meeting.class); + Meeting m2 = mock(Meeting.class); + + // when + // save 시 반환될 Attendance를 목으로 대체 + when(attendanceRepository.save(any(Attendance.class))) + .thenAnswer(invocation -> invocation.getArgument(0)); + + attendanceSaveService.init(user, List.of(m1, m2)); + + // then + // 저장은 2회, user.add는 2회 + verify(attendanceRepository, times(2)).save(any(Attendance.class)); + verify(user, times(2)).add(any(Attendance.class)); + } + + @Test + @DisplayName("saveAll(users, meeting): 사용자 수만큼 Attendance 생성 후 saveAll 호출") + void saveAll_bulkInsert() { + // given + User u1 = mock(User.class); + User u2 = mock(User.class); + Meeting meeting = mock(Meeting.class); + + // when + attendanceSaveService.saveAll(List.of(u1, u2), meeting); + + // then + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(attendanceRepository).saveAll(captor.capture()); + + List saved = captor.getValue(); + Assertions.assertThat(saved).hasSize(2); + } +} From 18553954c61deecd603e4035d72b021124c8a92c Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 17:36:34 +0900 Subject: [PATCH 040/114] =?UTF-8?q?test=20:=20AttendanceUpdate=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=EB=8B=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AttendanceUpdateServiceTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java diff --git a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java new file mode 100644 index 00000000..889f45af --- /dev/null +++ b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java @@ -0,0 +1,79 @@ +package leets.weeth.domain.attendance.domain.service; + +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import leets.weeth.domain.attendance.domain.entity.Attendance; +import leets.weeth.domain.attendance.domain.entity.enums.Status; +import leets.weeth.domain.user.domain.entity.User; + +public class AttendanceUpdateServiceTest { + + private final AttendanceUpdateService attendanceUpdateService = new AttendanceUpdateService(); + + @Test + @DisplayName("attend(): attendance.attend() + user.attend() 호출") + void attend_callsEntityMethods() { + // given + Attendance attendance = mock(Attendance.class); + User user = mock(User.class); + when(attendance.getUser()).thenReturn(user); + + // when + attendanceUpdateService.attend(attendance); + + // then + verify(attendance).attend(); + verify(user).attend(); + } + + @Test + @DisplayName("close(): pending만 close() + user.absent() 호출") + void close_onlyPendingIsClosed() { + // given + Attendance pending = mock(Attendance.class); + Attendance nonPending = mock(Attendance.class); + User pendingUser = mock(User.class); + User nonPendingUser = mock(User.class); + + when(pending.isPending()).thenReturn(true); + when(nonPending.isPending()).thenReturn(false); + when(pending.getUser()).thenReturn(pendingUser); + when(nonPending.getUser()).thenReturn(nonPendingUser); + + // when + attendanceUpdateService.close(List.of(pending, nonPending)); + + // then + verify(pending).close(); + verify(pendingUser).absent(); + verify(nonPending, never()).close(); + verify(nonPendingUser, never()).absent(); + } + + @Test + @DisplayName("updateUserAttendanceByStatus가 ATTEND면 user.removeAttend(), 그 외에는 user.removeAbsent() 처리") + void updateUserAttendanceByStatus() { + // given + Attendance attend = mock(Attendance.class); + Attendance absent = mock(Attendance.class); + User userA = mock(User.class); + User userB = mock(User.class); + + when(attend.getStatus()).thenReturn(Status.ATTEND); + when(absent.getStatus()).thenReturn(Status.ABSENT); + when(attend.getUser()).thenReturn(userA); + when(absent.getUser()).thenReturn(userB); + + // when + attendanceUpdateService.updateUserAttendanceByStatus(List.of(attend, absent)); + + // then + verify(userA).removeAttend(); + verify(userB).removeAbsent(); + } +} From 0fcc5bcd1f434aa992ec5085ceadf60f3fab629b Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 18:29:54 +0900 Subject: [PATCH 041/114] =?UTF-8?q?test=20:=20Attendance=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9=ED=95=A0=20Fixture=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/fixture/AttendanceTestFixture.java | 66 +++++++++++++++++++ .../test/fixture/ScheduleTestFixture.java | 30 +++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java create mode 100644 src/test/java/leets/weeth/domain/schedule/test/fixture/ScheduleTestFixture.java diff --git a/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java b/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java new file mode 100644 index 00000000..c5989aae --- /dev/null +++ b/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java @@ -0,0 +1,66 @@ +package leets.weeth.domain.attendance.test.fixture; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import leets.weeth.domain.attendance.domain.entity.Attendance; +import leets.weeth.domain.schedule.domain.entity.Meeting; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Status; + +public class AttendanceTestFixture { + + private AttendanceTestFixture() {} + + //todo : 추후 User Fixture 활용 예정 + public static User createActiveUser(String name) { + return User.builder().name(name).status(Status.ACTIVE).build(); + } + + //todo : 추후 User Fixture 활용 예정 + public static User createActiveUserWithAttendances(String name, List meetings) { + User user = createActiveUser(name); + + if (user.getAttendances() == null) { + try { + java.lang.reflect.Field f = user.getClass().getDeclaredField("attendances"); + f.setAccessible(true); + f.set(user, new java.util.ArrayList<>()); + } catch (Exception ignore) {} + } + if (meetings != null) { + for (Meeting meeting : meetings) { + Attendance attendance = createAttendance(meeting, user); + user.add(attendance); + } + } + return user; + } + + public static Attendance createAttendance(Meeting meeting, User user) { + return new Attendance(meeting, user); + } + + public static Meeting createOneDayMeeting(LocalDate date, int cardinal, int code, String title) { + return Meeting.builder() + .title(title) + .location("Test Location") + .start(date.atTime(10, 0)) + .end(date.atTime(12, 0)) + .code(code) + .cardinal(cardinal) + .build(); + } + + public static Meeting createInProgressMeeting(int cardinal, int code, String title) { + return Meeting.builder() + .title(title) + .location("Test Location") + .start(LocalDateTime.now().minusMinutes(5)) + .end(LocalDateTime.now().plusMinutes(5)) + .code(code) + .cardinal(cardinal) + .build(); + } +} diff --git a/src/test/java/leets/weeth/domain/schedule/test/fixture/ScheduleTestFixture.java b/src/test/java/leets/weeth/domain/schedule/test/fixture/ScheduleTestFixture.java new file mode 100644 index 00000000..92fcf7b3 --- /dev/null +++ b/src/test/java/leets/weeth/domain/schedule/test/fixture/ScheduleTestFixture.java @@ -0,0 +1,30 @@ +package leets.weeth.domain.schedule.test.fixture; + +import java.time.LocalDateTime; + +import leets.weeth.domain.schedule.domain.entity.Event; +import leets.weeth.domain.schedule.domain.entity.Meeting; + +public class ScheduleTestFixture { + + public static Event createEvent() { + return Event.builder() + .title("Test Meeting") + .location("Test Location") + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusDays(2)) + .cardinal(1) + .build(); + } + + public static Meeting createMeeting() { + return Meeting.builder() + .title("Test Meeting") + .location("Test Location") + .start(LocalDateTime.now()) + .end(LocalDateTime.now().plusDays(2)) + .code(1234) + .cardinal(1) + .build(); + } +} From 2602a652eaefc2bf1badda8db2754525dbaf9d3c Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 18:30:20 +0900 Subject: [PATCH 042/114] =?UTF-8?q?test=20:=20Attendance=20=EC=9C=A0?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=B2=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=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 --- .../AttendanceUseCaseImplTest.java | 240 ++++++++++++++++++ .../repository/AttendanceRepositoryTest.java | 9 +- 2 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java diff --git a/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java b/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java new file mode 100644 index 00000000..ff757b50 --- /dev/null +++ b/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java @@ -0,0 +1,240 @@ +package leets.weeth.domain.attendance.application; + +import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.time.LocalDate; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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 leets.weeth.domain.attendance.application.dto.AttendanceDTO; +import leets.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; +import leets.weeth.domain.attendance.application.exception.AttendanceNotFoundException; +import leets.weeth.domain.attendance.application.mapper.AttendanceMapper; +import leets.weeth.domain.attendance.application.usecase.AttendanceUseCaseImpl; +import leets.weeth.domain.attendance.domain.entity.Attendance; +import leets.weeth.domain.attendance.domain.entity.enums.Status; +import leets.weeth.domain.attendance.domain.service.AttendanceGetService; +import leets.weeth.domain.attendance.domain.service.AttendanceUpdateService; +import leets.weeth.domain.schedule.application.exception.MeetingNotFoundException; +import leets.weeth.domain.schedule.domain.entity.Meeting; +import leets.weeth.domain.schedule.domain.service.MeetingGetService; +import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.service.UserCardinalGetService; +import leets.weeth.domain.user.domain.service.UserGetService; + +@ExtendWith(MockitoExtension.class) +public class AttendanceUseCaseImplTest { + + private final Long userId = 10L; + @Mock private UserGetService userGetService; + @Mock private UserCardinalGetService userCardinalGetService; + @Mock private AttendanceGetService attendanceGetService; + @Mock private AttendanceUpdateService attendanceUpdateService; + @Mock private AttendanceMapper attendanceMapper; + @Mock private MeetingGetService meetingGetService; + @InjectMocks private AttendanceUseCaseImpl attendanceUseCase; + + @Test + @DisplayName("find: 오늘 날짜의 정기모임이 있으면 해당 정기모임으로 Main 매핑") + void find_todayMeeting() { + // given + LocalDate today = LocalDate.now(); + Meeting todayMeeting = createOneDayMeeting(today, 1, 1111, "Today Meeting"); + User user = createActiveUserWithAttendances("이지훈", List.of(todayMeeting)); + Attendance todayAttendance = user.getAttendances().get(0); + + when(userGetService.find(userId)).thenReturn(user); + when(attendanceMapper.toMainDto(eq(user), eq(todayAttendance))) + .thenReturn(mock(AttendanceDTO.Main.class)); + + // when + AttendanceDTO.Main actual = attendanceUseCase.find(userId); + + // then + assertThat(actual).isNotNull(); + verify(attendanceMapper).toMainDto(eq(user), eq(todayAttendance)); + } + + @Test + @DisplayName("findAllDetailsByCurrentCardinal: 현재 기수만 필터링·정렬하여 Detail 매핑") + void findAllDetailsByCurrentCardinal() { + // given + LocalDate today = LocalDate.now(); + Meeting meetingDayMinus1 = createOneDayMeeting(today.minusDays(1), 1, 1111, "D-1"); + Meeting meetingToday = createOneDayMeeting(today, 1, 2222, "D-Day"); + User user = createActiveUserWithAttendances("이지훈", List.of(meetingDayMinus1, meetingToday)); + + List userAttendances = user.getAttendances(); + Attendance attendanceFirst = userAttendances.get(0); // D-1 + Attendance attendanceSecond = userAttendances.get(1); // D-Day + + when(userGetService.find(userId)).thenReturn(user); + Cardinal currentCardinal = mock(Cardinal.class); + when(currentCardinal.getCardinalNumber()).thenReturn(1); + when(userCardinalGetService.getCurrentCardinal(user)).thenReturn(currentCardinal); + + AttendanceDTO.Response responseFirst = mock(AttendanceDTO.Response.class); + AttendanceDTO.Response responseSecond = mock(AttendanceDTO.Response.class); + when(attendanceMapper.toResponseDto(attendanceFirst)).thenReturn(responseFirst); + when(attendanceMapper.toResponseDto(attendanceSecond)).thenReturn(responseSecond); + + AttendanceDTO.Detail expectedDetail = mock(AttendanceDTO.Detail.class); + when(attendanceMapper.toDetailDto(eq(user), anyList())).thenReturn(expectedDetail); + + // when + AttendanceDTO.Detail actualDetail = attendanceUseCase.findAllDetailsByCurrentCardinal(userId); + + // then + assertThat(actualDetail).isSameAs(expectedDetail); + verify(attendanceMapper).toDetailDto(eq(user), argThat(list -> list.size() == 2)); + } + + @Test + @DisplayName("findAllAttendanceByMeeting: 정기모임 조회 후 해당 출석들을 DTO로 매핑") + void findAllAttendanceByMeeting() { + // given + Meeting meeting = createOneDayMeeting(LocalDate.now(), 1, 1111, "Today"); + Attendance a1 = createAttendance(meeting, createActiveUser("A")); + Attendance a2 = createAttendance(meeting, createActiveUser("B")); + + when(meetingGetService.find(99L)).thenReturn(meeting); + when(attendanceGetService.findAllByMeeting(meeting)).thenReturn(List.of(a1, a2)); + AttendanceDTO.AttendanceInfo i1 = mock(AttendanceDTO.AttendanceInfo.class); + AttendanceDTO.AttendanceInfo i2 = mock(AttendanceDTO.AttendanceInfo.class); + when(attendanceMapper.toAttendanceInfoDto(a1)).thenReturn(i1); + when(attendanceMapper.toAttendanceInfoDto(a2)).thenReturn(i2); + + // when + List actual = attendanceUseCase.findAllAttendanceByMeeting(99L); + + // then + assertThat(actual).containsExactly(i1, i2); + } + + @Test + @DisplayName("close(now, cardinal): 당일 정기모임을 찾아 close") + void close_success() { + // given + LocalDate now = LocalDate.now(); + Meeting targetMeeting = createOneDayMeeting(now, 1, 1111, "Today"); + Meeting otherMeeting = createOneDayMeeting(now.minusDays(1), 1, 9999, "Yesterday"); + + Attendance attendance1 = mock(Attendance.class); + Attendance attendance2 = mock(Attendance.class); + + when(meetingGetService.find(1)).thenReturn(List.of(targetMeeting, otherMeeting)); + when(attendanceGetService.findAllByMeeting(targetMeeting)).thenReturn(List.of(attendance1, attendance2)); + + // when + attendanceUseCase.close(now, 1); + + // then + verify(attendanceUpdateService).close(argThat(list -> + list.size() == 2 && list.containsAll(List.of(attendance1, attendance2)) + )); + } + + @Test + @DisplayName("close(now, cardinal): 당일 정기모임이 없으면 MeetingNotFoundException") + void close_notFound() { + // given + LocalDate now = LocalDate.now(); + Meeting otherDayMeeting = createOneDayMeeting(now.minusDays(1), 1, 9999, "Yesterday"); + + when(meetingGetService.find(1)).thenReturn(List.of(otherDayMeeting)); + + // when & then + assertThatThrownBy(() -> attendanceUseCase.close(now, 1)) + .isInstanceOf(MeetingNotFoundException.class); + } + + @Nested + @DisplayName("checkIn") + class CheckInTest { + + @Test + @DisplayName("진행 중 정기모임이고 코드 일치하며 상태가 ATTEND가 아니면 출석 처리") + void checkIn_success() { + // given + User user = mock(User.class); + Meeting inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress"); + Attendance attendance = mock(Attendance.class); + when(attendance.getMeeting()).thenReturn(inProgressMeeting); + when(attendance.isWrong(1234)).thenReturn(false); + when(attendance.getStatus()).thenReturn(Status.PENDING); + + when(userGetService.find(userId)).thenReturn(user); + when(user.getAttendances()).thenReturn(List.of(attendance)); + + // when + attendanceUseCase.checkIn(userId, 1234); + + // then + verify(attendanceUpdateService).attend(attendance); + } + + @Test + @DisplayName("진행 중 정기모임이 없으면 AttendanceNotFoundException") + void checkIn_notFoundMeeting() { + // given + User user = mock(User.class); + when(userGetService.find(userId)).thenReturn(user); + when(user.getAttendances()).thenReturn(List.of()); + + // when & then + assertThatThrownBy(() -> attendanceUseCase.checkIn(userId, 1234)) + .isInstanceOf(AttendanceNotFoundException.class); + } + + @Test + @DisplayName("코드 불일치 시 AttendanceCodeMismatchException") + void checkIn_wrongCode() { + // given + User user = mock(User.class); + Meeting inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress"); + + Attendance attendance = mock(Attendance.class); + when(attendance.getMeeting()).thenReturn(inProgressMeeting); + when(attendance.isWrong(9999)).thenReturn(true); + + when(userGetService.find(userId)).thenReturn(user); + when(user.getAttendances()).thenReturn(List.of(attendance)); + + // when & then + assertThatThrownBy(() -> attendanceUseCase.checkIn(userId, 9999)) + .isInstanceOf(AttendanceCodeMismatchException.class); + } + + @Test + @DisplayName("이미 ATTEND면 추가 처리 없이 종료") + void checkIn_alreadyAttend() { + // given + User user = mock(User.class); + Meeting inProgressMeeting = createInProgressMeeting(1, 1234, "InProgress"); + + Attendance attendance = mock(Attendance.class); + when(attendance.getMeeting()).thenReturn(inProgressMeeting); + when(attendance.isWrong(1234)).thenReturn(false); + when(attendance.getStatus()).thenReturn(Status.ATTEND); + + when(userGetService.find(userId)).thenReturn(user); + when(user.getAttendances()).thenReturn(List.of(attendance)); + + // when + attendanceUseCase.checkIn(userId, 1234); + + // then + verify(attendanceUpdateService, never()).attend(any()); + } + } +} diff --git a/src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java b/src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java index 55608158..e41ab946 100644 --- a/src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/attendance/domain/repository/AttendanceRepositoryTest.java @@ -58,12 +58,17 @@ void setUp() { @Test @DisplayName("특정 정기모임 + 사용자 상태로 출석 목록 조회") void findAllByMeetingAndUserStatus() { + // given + Status active = activeUser1.getStatus(); + // when - List attendances = attendanceRepository.findAllByMeetingAndUserStatus(meeting, Status.ACTIVE); + List attendances = + attendanceRepository.findAllByMeetingAndUserStatus(meeting, active); // then assertThat(attendances).hasSize(2); - assertThat(attendances).extracting(a -> a.getUser().getName()) + assertThat(attendances) + .extracting(a -> a.getUser().getName()) .containsExactlyInAnyOrder("이지훈", "이강혁"); } From c3efaeabaad06ffddeef00d9fe2ac7e599e49b01 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 18:37:47 +0900 Subject: [PATCH 043/114] =?UTF-8?q?test=20:=20Fixture=20=ED=99=9C=EC=9A=A9?= =?UTF-8?q?=ED=95=B4=EC=84=9C=20Attendance=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=EC=97=90=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/AttendanceSaveServiceTest.java | 38 +++++--- .../service/AttendanceUpdateServiceTest.java | 92 ++++++++++++------- 2 files changed, 83 insertions(+), 47 deletions(-) diff --git a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java index 4ea5d390..fd16176f 100644 --- a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java +++ b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java @@ -1,5 +1,7 @@ package leets.weeth.domain.attendance.domain.service; +import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; +import static leets.weeth.domain.schedule.test.fixture.ScheduleTestFixture.*; import static org.mockito.Mockito.*; import java.util.List; @@ -19,7 +21,7 @@ import leets.weeth.domain.user.domain.entity.User; @ExtendWith(MockitoExtension.class) -public class AttendanceSaveServiceTest { +class AttendanceSaveServiceTest { @Mock private AttendanceRepository attendanceRepository; @InjectMocks private AttendanceSaveService attendanceSaveService; @@ -29,18 +31,17 @@ public class AttendanceSaveServiceTest { void init_createsAttendanceAndLinkToUser() { // given User user = mock(User.class); - Meeting m1 = mock(Meeting.class); - Meeting m2 = mock(Meeting.class); + Meeting meetingFirst = createMeeting(); + Meeting meetingSecond = createMeeting(); - // when - // save 시 반환될 Attendance를 목으로 대체 + // save가 새로운 Attendance를 반환하는 동작을 그대로 흉내 (인자로 받은 객체를 그대로 반환) when(attendanceRepository.save(any(Attendance.class))) .thenAnswer(invocation -> invocation.getArgument(0)); - attendanceSaveService.init(user, List.of(m1, m2)); + // when + attendanceSaveService.init(user, List.of(meetingFirst, meetingSecond)); // then - // 저장은 2회, user.add는 2회 verify(attendanceRepository, times(2)).save(any(Attendance.class)); verify(user, times(2)).add(any(Attendance.class)); } @@ -49,18 +50,25 @@ void init_createsAttendanceAndLinkToUser() { @DisplayName("saveAll(users, meeting): 사용자 수만큼 Attendance 생성 후 saveAll 호출") void saveAll_bulkInsert() { // given - User u1 = mock(User.class); - User u2 = mock(User.class); - Meeting meeting = mock(Meeting.class); + Meeting meeting = createMeeting(); + User userFirst = createActiveUser("이지훈"); + User userSecond = createActiveUser("이강혁"); // when - attendanceSaveService.saveAll(List.of(u1, u2), meeting); + attendanceSaveService.saveAll(List.of(userFirst, userSecond), meeting); // then - ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); - verify(attendanceRepository).saveAll(captor.capture()); + @SuppressWarnings("unchecked") + ArgumentCaptor> listCaptor = ArgumentCaptor.forClass(List.class); + verify(attendanceRepository).saveAll(listCaptor.capture()); + + List savedAttendances = listCaptor.getValue(); + Assertions.assertThat(savedAttendances).hasSize(2); - List saved = captor.getValue(); - Assertions.assertThat(saved).hasSize(2); + Assertions.assertThat(savedAttendances) + .allSatisfy(att -> Assertions.assertThat(att.getMeeting()).isSameAs(meeting)); + Assertions.assertThat(savedAttendances) + .extracting(Attendance::getUser) + .containsExactlyInAnyOrder(userFirst, userSecond); } } diff --git a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java index 889f45af..2682ac48 100644 --- a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java +++ b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceUpdateServiceTest.java @@ -1,5 +1,7 @@ package leets.weeth.domain.attendance.domain.service; +import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; +import static leets.weeth.domain.schedule.test.fixture.ScheduleTestFixture.*; import static org.mockito.Mockito.*; import java.util.List; @@ -9,9 +11,10 @@ import leets.weeth.domain.attendance.domain.entity.Attendance; import leets.weeth.domain.attendance.domain.entity.enums.Status; +import leets.weeth.domain.schedule.domain.entity.Meeting; import leets.weeth.domain.user.domain.entity.User; -public class AttendanceUpdateServiceTest { +class AttendanceUpdateServiceTest { private final AttendanceUpdateService attendanceUpdateService = new AttendanceUpdateService(); @@ -19,61 +22,86 @@ public class AttendanceUpdateServiceTest { @DisplayName("attend(): attendance.attend() + user.attend() 호출") void attend_callsEntityMethods() { // given - Attendance attendance = mock(Attendance.class); - User user = mock(User.class); - when(attendance.getUser()).thenReturn(user); + Meeting meeting = createMeeting(); + User realUser = createActiveUser("이지훈"); + User userSpy = spy(realUser); + + doNothing().when(userSpy).attend(); + + Attendance realAttendance = createAttendance(meeting, userSpy); + Attendance attendanceSpy = spy(realAttendance); // when - attendanceUpdateService.attend(attendance); + attendanceUpdateService.attend(attendanceSpy); // then - verify(attendance).attend(); - verify(user).attend(); + verify(attendanceSpy).attend(); + verify(userSpy).attend(); } @Test @DisplayName("close(): pending만 close() + user.absent() 호출") void close_onlyPendingIsClosed() { // given - Attendance pending = mock(Attendance.class); - Attendance nonPending = mock(Attendance.class); - User pendingUser = mock(User.class); - User nonPendingUser = mock(User.class); + Meeting meeting = createMeeting(); + + User pendingUserReal = createActiveUser("pending-user"); + User nonPendingUserReal = createActiveUser("non-pending-user"); + User pendingUserSpy = spy(pendingUserReal); + User nonPendingUserSpy = spy(nonPendingUserReal); + + doNothing().when(pendingUserSpy).absent(); + doNothing().when(nonPendingUserSpy).absent(); + + Attendance pendingAttendanceReal = createAttendance(meeting, pendingUserSpy); + Attendance nonPendingAttendanceReal = createAttendance(meeting, nonPendingUserSpy); + Attendance pendingAttendanceSpy = spy(pendingAttendanceReal); + Attendance nonPendingAttendanceSpy = spy(nonPendingAttendanceReal); - when(pending.isPending()).thenReturn(true); - when(nonPending.isPending()).thenReturn(false); - when(pending.getUser()).thenReturn(pendingUser); - when(nonPending.getUser()).thenReturn(nonPendingUser); + doReturn(true).when(pendingAttendanceSpy).isPending(); + doReturn(false).when(nonPendingAttendanceSpy).isPending(); // when - attendanceUpdateService.close(List.of(pending, nonPending)); + attendanceUpdateService.close(List.of(pendingAttendanceSpy, nonPendingAttendanceSpy)); // then - verify(pending).close(); - verify(pendingUser).absent(); - verify(nonPending, never()).close(); - verify(nonPendingUser, never()).absent(); + verify(pendingAttendanceSpy).close(); + verify(pendingUserSpy).absent(); + + verify(nonPendingAttendanceSpy, never()).close(); + verify(nonPendingUserSpy, never()).absent(); } @Test - @DisplayName("updateUserAttendanceByStatus가 ATTEND면 user.removeAttend(), 그 외에는 user.removeAbsent() 처리") + @DisplayName("updateUserAttendanceByStatus: ATTEND면 user.removeAttend(), 그 외에는 user.removeAbsent()") void updateUserAttendanceByStatus() { // given - Attendance attend = mock(Attendance.class); - Attendance absent = mock(Attendance.class); - User userA = mock(User.class); - User userB = mock(User.class); + Meeting meeting = createMeeting(); + + User attendUserReal = createActiveUser("attend-user"); + User absentUserReal = createActiveUser("absent-user"); + + User attendUserSpy = spy(attendUserReal); + User absentUserSpy = spy(absentUserReal); + + doNothing().when(attendUserSpy).removeAttend(); + doNothing().when(absentUserSpy).removeAbsent(); + + Attendance attendAttendanceReal = createAttendance(meeting, attendUserSpy); + Attendance absentAttendanceReal = createAttendance(meeting, absentUserSpy); + Attendance attendAttendanceSpy = spy(attendAttendanceReal); + Attendance absentAttendanceSpy = spy(absentAttendanceReal); - when(attend.getStatus()).thenReturn(Status.ATTEND); - when(absent.getStatus()).thenReturn(Status.ABSENT); - when(attend.getUser()).thenReturn(userA); - when(absent.getUser()).thenReturn(userB); + doReturn(Status.ATTEND).when(attendAttendanceSpy).getStatus(); + doReturn(Status.ABSENT).when(absentAttendanceSpy).getStatus(); + doReturn(attendUserSpy).when(attendAttendanceSpy).getUser(); + doReturn(absentUserSpy).when(absentAttendanceSpy).getUser(); // when - attendanceUpdateService.updateUserAttendanceByStatus(List.of(attend, absent)); + attendanceUpdateService.updateUserAttendanceByStatus(List.of(attendAttendanceSpy, absentAttendanceSpy)); // then - verify(userA).removeAttend(); - verify(userB).removeAbsent(); + verify(attendUserSpy).removeAttend(); + verify(absentUserSpy).removeAbsent(); } } From 57688d3f3494174ec4ff1fc602eadede0edf7def Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 18:38:18 +0900 Subject: [PATCH 044/114] =?UTF-8?q?test=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/domain/service/AttendanceSaveServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java index fd16176f..343edd12 100644 --- a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java +++ b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java @@ -34,7 +34,6 @@ void init_createsAttendanceAndLinkToUser() { Meeting meetingFirst = createMeeting(); Meeting meetingSecond = createMeeting(); - // save가 새로운 Attendance를 반환하는 동작을 그대로 흉내 (인자로 받은 객체를 그대로 반환) when(attendanceRepository.save(any(Attendance.class))) .thenAnswer(invocation -> invocation.getArgument(0)); From 7d7b5eec19215385da984932c5e3bcd937550686 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Sun, 26 Oct 2025 18:40:22 +0900 Subject: [PATCH 045/114] =?UTF-8?q?test=20:=20DisplayName=20=EC=A7=81?= =?UTF-8?q?=EA=B4=80=EC=A0=81=EC=9D=B8=20=EC=9D=B4=EB=A6=84=EC=9C=BC?= =?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 --- .../attendance/domain/service/AttendanceSaveServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java index 343edd12..b8b8c45e 100644 --- a/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java +++ b/src/test/java/leets/weeth/domain/attendance/domain/service/AttendanceSaveServiceTest.java @@ -27,7 +27,7 @@ class AttendanceSaveServiceTest { @InjectMocks private AttendanceSaveService attendanceSaveService; @Test - @DisplayName("init(user, meetings): 각 미팅에 대한 Attendance 저장 후 user.add 호출") + @DisplayName("init(user, meetings): 각 정기모임에 대한 Attendance 저장 후 user.add 호출") void init_createsAttendanceAndLinkToUser() { // given User user = mock(User.class); From f03fd3ef9304ecc2fc2d42ba0e4d2818f879a368 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 27 Oct 2025 21:39:39 +0900 Subject: [PATCH 046/114] =?UTF-8?q?feat:=20board=20=EC=83=9D=EC=84=B1,=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=8B=9C=20ID=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20dto=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/application/dto/NoticeDTO.java | 8 ++++ .../domain/board/application/dto/PostDTO.java | 37 ++++++++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/main/java/leets/weeth/domain/board/application/dto/NoticeDTO.java b/src/main/java/leets/weeth/domain/board/application/dto/NoticeDTO.java index 507b5017..1b56c004 100644 --- a/src/main/java/leets/weeth/domain/board/application/dto/NoticeDTO.java +++ b/src/main/java/leets/weeth/domain/board/application/dto/NoticeDTO.java @@ -1,5 +1,6 @@ package leets.weeth.domain.board.application.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import leets.weeth.domain.comment.application.dto.CommentDTO; @@ -59,4 +60,11 @@ public record ResponseAll( ) { } + @Builder + public record SaveResponse( + @Schema(description = "공지사항 생성 응답", example = "1") + long id + ) { + } + } diff --git a/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java b/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java index 6fd4f944..bcdc38fb 100644 --- a/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java +++ b/src/main/java/leets/weeth/domain/board/application/dto/PostDTO.java @@ -1,10 +1,9 @@ package leets.weeth.domain.board.application.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; -import java.util.List; import leets.weeth.domain.board.domain.entity.enums.Category; import leets.weeth.domain.board.domain.entity.enums.Part; import leets.weeth.domain.comment.application.dto.CommentDTO; @@ -14,6 +13,9 @@ import leets.weeth.domain.user.domain.entity.enums.Role; import lombok.Builder; +import java.time.LocalDateTime; +import java.util.List; + public class PostDTO { @Builder @@ -26,7 +28,8 @@ public record Save( @NotNull Part part, @NotNull Integer cardinalNumber, @Valid List<@NotNull FileSaveRequest> files - ){} + ) { + } @Builder public record SaveEducation( @@ -35,7 +38,15 @@ public record SaveEducation( @NotNull List parts, @NotNull Integer cardinalNumber, @Valid List<@NotNull FileSaveRequest> files - ){} + ) { + } + + @Builder + public record SaveResponse( + @Schema(description = "게시글 생성시 응답", example = "1") + long id + ) { + } @Builder public record Update( @@ -46,7 +57,8 @@ public record Update( Part part, Integer cardinalNumber, @Valid List files - ){} + ) { + } @Builder public record UpdateEducation( @@ -55,7 +67,8 @@ public record UpdateEducation( List parts, Integer cardinalNumber, @Valid List files - ){} + ) { + } @Builder public record Response( @@ -74,7 +87,8 @@ public record Response( Integer commentCount, List comments, List fileUrls - ){} + ) { + } @Builder public record ResponseAll( @@ -91,7 +105,8 @@ public record ResponseAll( Integer commentCount, boolean hasFile, boolean isNew - ){} + ) { + } @Builder public record ResponseEducationAll( @@ -106,9 +121,11 @@ public record ResponseEducationAll( Integer commentCount, boolean hasFile, boolean isNew - ){} + ) { + } public record ResponseStudyNames( List studyNames - ) {} + ) { + } } From e2dbdf598703c9734cefa1b77b485b4bf54dc061 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 27 Oct 2025 21:40:43 +0900 Subject: [PATCH 047/114] =?UTF-8?q?feat:=20board=20=EC=83=9D=EC=84=B1,=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=8B=9C=20id=20=EC=A6=89=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/NoticeMapper.java | 2 ++ .../board/application/mapper/PostMapper.java | 2 ++ .../application/usecase/NoticeUsecase.java | 5 ++- .../usecase/NoticeUsecaseImpl.java | 19 ++++++---- .../application/usecase/PostUseCaseImpl.java | 36 ++++++++++--------- .../application/usecase/PostUsecase.java | 8 ++--- .../domain/service/NoticeSaveService.java | 5 ++- .../board/domain/service/PostSaveService.java | 6 ++-- .../EducationAdminController.java | 26 ++++++-------- .../presentation/NoticeAdminController.java | 20 +++++------ .../board/presentation/PostController.java | 36 ++++++------------- 11 files changed, 76 insertions(+), 89 deletions(-) diff --git a/src/main/java/leets/weeth/domain/board/application/mapper/NoticeMapper.java b/src/main/java/leets/weeth/domain/board/application/mapper/NoticeMapper.java index fd39cffb..9509e24a 100644 --- a/src/main/java/leets/weeth/domain/board/application/mapper/NoticeMapper.java +++ b/src/main/java/leets/weeth/domain/board/application/mapper/NoticeMapper.java @@ -37,4 +37,6 @@ public interface NoticeMapper { }) NoticeDTO.Response toNoticeDto(Notice notice, List fileUrls, List comments); + NoticeDTO.SaveResponse toSaveResponse(Notice notice); + } diff --git a/src/main/java/leets/weeth/domain/board/application/mapper/PostMapper.java b/src/main/java/leets/weeth/domain/board/application/mapper/PostMapper.java index 95b19909..1bbade14 100644 --- a/src/main/java/leets/weeth/domain/board/application/mapper/PostMapper.java +++ b/src/main/java/leets/weeth/domain/board/application/mapper/PostMapper.java @@ -34,6 +34,8 @@ public interface PostMapper { @Mapping(target = "category", constant = "Education") Post fromEducationDto(PostDTO.SaveEducation dto, User user); + PostDTO.SaveResponse toSaveResponse(Post post); + @Mappings({ @Mapping(target = "name", source = "post.user.name"), @Mapping(target = "position", source = "post.user.position"), diff --git a/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecase.java b/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecase.java index 00f20557..708e2061 100644 --- a/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecase.java +++ b/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecase.java @@ -6,14 +6,13 @@ public interface NoticeUsecase { - - void save(NoticeDTO.Save dto, Long userId); + NoticeDTO.SaveResponse save(NoticeDTO.Save dto, Long userId); NoticeDTO.Response findNotice(Long noticeId); Slice findNotices(int pageNumber, int pageSize); - void update(Long noticeId, NoticeDTO.Update dto, Long userId) throws UserNotMatchException; + NoticeDTO.SaveResponse update(Long noticeId, NoticeDTO.Update dto, Long userId) throws UserNotMatchException; void delete(Long noticeId, Long userId) throws UserNotMatchException; diff --git a/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImpl.java b/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImpl.java index a04c3bc8..bcbe76ac 100644 --- a/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImpl.java +++ b/src/main/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImpl.java @@ -1,9 +1,5 @@ package leets.weeth.domain.board.application.usecase; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import leets.weeth.domain.board.application.dto.NoticeDTO; import leets.weeth.domain.board.application.exception.NoSearchResultException; import leets.weeth.domain.board.application.exception.PageNotFoundException; @@ -33,6 +29,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class NoticeUsecaseImpl implements NoticeUsecase { @@ -54,14 +55,16 @@ public class NoticeUsecaseImpl implements NoticeUsecase { @Override @Transactional - public void save(NoticeDTO.Save request, Long userId) { + public NoticeDTO.SaveResponse save(NoticeDTO.Save request, Long userId) { User user = userGetService.find(userId); Notice notice = mapper.fromNoticeDto(request, user); - noticeSaveService.save(notice); + Notice savedNotice = noticeSaveService.save(notice); List files = fileMapper.toFileList(request.files(), notice); fileSaveService.save(files); + + return mapper.toSaveResponse(savedNotice); } @Override @@ -103,7 +106,7 @@ public Slice searchNotice(String keyword, int pageNumber, @Override @Transactional - public void update(Long noticeId, NoticeDTO.Update dto, Long userId) { + public NoticeDTO.SaveResponse update(Long noticeId, NoticeDTO.Update dto, Long userId) { Notice notice = validateOwner(noticeId, userId); List fileList = getFiles(noticeId); @@ -113,6 +116,8 @@ public void update(Long noticeId, NoticeDTO.Update dto, Long userId) { fileSaveService.save(files); noticeUpdateService.update(notice, dto); + + return mapper.toSaveResponse(notice); } @Override diff --git a/src/main/java/leets/weeth/domain/board/application/usecase/PostUseCaseImpl.java b/src/main/java/leets/weeth/domain/board/application/usecase/PostUseCaseImpl.java index 1ad0e60e..b213258c 100644 --- a/src/main/java/leets/weeth/domain/board/application/usecase/PostUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/board/application/usecase/PostUseCaseImpl.java @@ -1,9 +1,5 @@ package leets.weeth.domain.board.application.usecase; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import leets.weeth.domain.board.application.dto.PartPostDTO; import leets.weeth.domain.board.application.dto.PostDTO; import leets.weeth.domain.board.application.exception.CategoryAccessDeniedException; @@ -33,14 +29,15 @@ import leets.weeth.domain.user.domain.service.UserCardinalGetService; import leets.weeth.domain.user.domain.service.UserGetService; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class PostUseCaseImpl implements PostUsecase { @@ -64,7 +61,7 @@ public class PostUseCaseImpl implements PostUsecase { @Override @Transactional - public void save(PostDTO.Save request, Long userId) { + public PostDTO.SaveResponse save(PostDTO.Save request, Long userId) { User user = userGetService.find(userId); if (request.category() == Category.Education @@ -74,23 +71,26 @@ public void save(PostDTO.Save request, Long userId) { cardinalGetService.findByUserSide(request.cardinalNumber()); Post post = mapper.fromPostDto(request, user); - postSaveService.save(post); + Post savedPost = postSaveService.save(post); List files = fileMapper.toFileList(request.files(), post); fileSaveService.save(files); + + return mapper.toSaveResponse(savedPost); } @Override @Transactional - public void saveEducation(PostDTO.SaveEducation request, Long userId) { + public PostDTO.SaveResponse saveEducation(PostDTO.SaveEducation request, Long userId) { User user = userGetService.find(userId); Post post = mapper.fromEducationDto(request, user); - - postSaveService.save(post); + Post saverPost = postSaveService.save(post); List files = fileMapper.toFileList(request.files(), post); fileSaveService.save(files); + + return mapper.toSaveResponse(saverPost); } @Override @@ -193,7 +193,7 @@ public Slice searchEducation(String keyword, int p @Override @Transactional - public void update(Long postId, PostDTO.Update dto, Long userId) { + public PostDTO.SaveResponse update(Long postId, PostDTO.Update dto, Long userId) { Post post = validateOwner(postId, userId); if (dto.files() != null) { @@ -205,11 +205,13 @@ public void update(Long postId, PostDTO.Update dto, Long userId) { } postUpdateService.update(post, dto); + + return mapper.toSaveResponse(post); } @Override @Transactional - public void updateEducation(Long postId, PostDTO.UpdateEducation dto, Long userId) { + public PostDTO.SaveResponse updateEducation(Long postId, PostDTO.UpdateEducation dto, Long userId) { Post post = validateOwner(postId, userId); if (dto.files() != null) { @@ -221,6 +223,8 @@ public void updateEducation(Long postId, PostDTO.UpdateEducation dto, Long userI } postUpdateService.updateEducation(post, dto); + + return mapper.toSaveResponse(post); } @Override diff --git a/src/main/java/leets/weeth/domain/board/application/usecase/PostUsecase.java b/src/main/java/leets/weeth/domain/board/application/usecase/PostUsecase.java index c2acb2e4..4755a26c 100644 --- a/src/main/java/leets/weeth/domain/board/application/usecase/PostUsecase.java +++ b/src/main/java/leets/weeth/domain/board/application/usecase/PostUsecase.java @@ -9,9 +9,9 @@ public interface PostUsecase { - void save(PostDTO.Save request, Long userId); + PostDTO.SaveResponse save(PostDTO.Save request, Long userId); - void saveEducation(PostDTO.SaveEducation request, Long userId); + PostDTO.SaveResponse saveEducation(PostDTO.SaveEducation request, Long userId); PostDTO.Response findPost(Long postId); @@ -23,9 +23,9 @@ public interface PostUsecase { PostDTO.ResponseStudyNames findStudyNames(Part part); - void update(Long postId, PostDTO.Update dto, Long userId) throws UserNotMatchException; + PostDTO.SaveResponse update(Long postId, PostDTO.Update dto, Long userId) throws UserNotMatchException; - void updateEducation(Long postId, PostDTO.UpdateEducation dto, Long userId) throws UserNotMatchException; + PostDTO.SaveResponse updateEducation(Long postId, PostDTO.UpdateEducation dto, Long userId) throws UserNotMatchException; void delete(Long postId, Long userId) throws UserNotMatchException; diff --git a/src/main/java/leets/weeth/domain/board/domain/service/NoticeSaveService.java b/src/main/java/leets/weeth/domain/board/domain/service/NoticeSaveService.java index b26e1061..30dbcfbf 100644 --- a/src/main/java/leets/weeth/domain/board/domain/service/NoticeSaveService.java +++ b/src/main/java/leets/weeth/domain/board/domain/service/NoticeSaveService.java @@ -1,6 +1,5 @@ package leets.weeth.domain.board.domain.service; -import jakarta.transaction.Transactional; import leets.weeth.domain.board.domain.entity.Notice; import leets.weeth.domain.board.domain.repository.NoticeRepository; import lombok.RequiredArgsConstructor; @@ -12,8 +11,8 @@ public class NoticeSaveService { private final NoticeRepository noticeRepository; - public void save(Notice notice){ - noticeRepository.save(notice); + public Notice save(Notice notice){ + return noticeRepository.save(notice); } } diff --git a/src/main/java/leets/weeth/domain/board/domain/service/PostSaveService.java b/src/main/java/leets/weeth/domain/board/domain/service/PostSaveService.java index 80cd5b55..c427b3c3 100644 --- a/src/main/java/leets/weeth/domain/board/domain/service/PostSaveService.java +++ b/src/main/java/leets/weeth/domain/board/domain/service/PostSaveService.java @@ -1,6 +1,5 @@ package leets.weeth.domain.board.domain.service; -import jakarta.transaction.Transactional; import leets.weeth.domain.board.domain.entity.Post; import leets.weeth.domain.board.domain.repository.PostRepository; import lombok.RequiredArgsConstructor; @@ -12,8 +11,7 @@ public class PostSaveService { private final PostRepository postRepository; - public void save(Post post) { - postRepository.save(post); + public Post save(Post post) { + return postRepository.save(post); } - } diff --git a/src/main/java/leets/weeth/domain/board/presentation/EducationAdminController.java b/src/main/java/leets/weeth/domain/board/presentation/EducationAdminController.java index 106eaa6c..196bc8cf 100644 --- a/src/main/java/leets/weeth/domain/board/presentation/EducationAdminController.java +++ b/src/main/java/leets/weeth/domain/board/presentation/EducationAdminController.java @@ -1,8 +1,5 @@ package leets.weeth.domain.board.presentation; -import static leets.weeth.domain.board.presentation.ResponseMessage.EDUCATION_UPDATED_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_CREATED_SUCCESS; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -13,12 +10,10 @@ import leets.weeth.global.auth.annotation.CurrentUser; import leets.weeth.global.common.response.CommonResponse; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import static leets.weeth.domain.board.presentation.ResponseMessage.EDUCATION_UPDATED_SUCCESS; +import static leets.weeth.domain.board.presentation.ResponseMessage.POST_CREATED_SUCCESS; @Tag(name = "EDUCATION ADMIN", description = "[ADMIN] 공지사항 교육자료 API") @RestController @@ -29,18 +24,19 @@ public class EducationAdminController { @PostMapping("/education") @Operation(summary = "교육자료 생성") - public CommonResponse saveEducation(@RequestBody @Valid PostDTO.SaveEducation dto, @Parameter(hidden = true) @CurrentUser Long userId) { - postUsecase.saveEducation(dto, userId); + public CommonResponse saveEducation(@RequestBody @Valid PostDTO.SaveEducation dto, @Parameter(hidden = true) @CurrentUser Long userId) { + PostDTO.SaveResponse response = postUsecase.saveEducation(dto, userId); - return CommonResponse.createSuccess(POST_CREATED_SUCCESS.getMessage()); + return CommonResponse.createSuccess(POST_CREATED_SUCCESS.getMessage(), response); } @PatchMapping(value = "/{boardId}") @Operation(summary="교육자료 게시글 수정") - public CommonResponse update(@PathVariable Long boardId, + public CommonResponse update(@PathVariable Long boardId, @RequestBody @Valid PostDTO.UpdateEducation dto, @Parameter(hidden = true) @CurrentUser Long userId) throws UserNotMatchException { - postUsecase.updateEducation(boardId, dto, userId); - return CommonResponse.createSuccess(EDUCATION_UPDATED_SUCCESS.getMessage()); + PostDTO.SaveResponse response = postUsecase.updateEducation(boardId, dto, userId); + + return CommonResponse.createSuccess(EDUCATION_UPDATED_SUCCESS.getMessage(), response); } } diff --git a/src/main/java/leets/weeth/domain/board/presentation/NoticeAdminController.java b/src/main/java/leets/weeth/domain/board/presentation/NoticeAdminController.java index 28f4bcc9..520736d6 100644 --- a/src/main/java/leets/weeth/domain/board/presentation/NoticeAdminController.java +++ b/src/main/java/leets/weeth/domain/board/presentation/NoticeAdminController.java @@ -6,15 +6,11 @@ import jakarta.validation.Valid; import leets.weeth.domain.board.application.dto.NoticeDTO; import leets.weeth.domain.board.application.usecase.NoticeUsecase; -import leets.weeth.global.auth.annotation.CurrentUser; import leets.weeth.domain.user.application.exception.UserNotMatchException; +import leets.weeth.global.auth.annotation.CurrentUser; import leets.weeth.global.common.response.CommonResponse; import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; import static leets.weeth.domain.board.presentation.ResponseMessage.*; @@ -28,19 +24,21 @@ public class NoticeAdminController { @PostMapping @Operation(summary="공지사항 생성") - public CommonResponse save(@RequestBody @Valid NoticeDTO.Save dto, + public CommonResponse save(@RequestBody @Valid NoticeDTO.Save dto, @Parameter(hidden = true) @CurrentUser Long userId) { - noticeUsecase.save(dto, userId); - return CommonResponse.createSuccess(NOTICE_CREATED_SUCCESS.getMessage()); + NoticeDTO.SaveResponse response = noticeUsecase.save(dto, userId); + + return CommonResponse.createSuccess(NOTICE_CREATED_SUCCESS.getMessage(), response); } @PatchMapping(value = "/{noticeId}") @Operation(summary="특정 공지사항 수정") - public CommonResponse update(@PathVariable Long noticeId, + public CommonResponse update(@PathVariable Long noticeId, @RequestBody @Valid NoticeDTO.Update dto, @Parameter(hidden = true) @CurrentUser Long userId) throws UserNotMatchException { - noticeUsecase.update(noticeId, dto, userId); - return CommonResponse.createSuccess(NOTICE_UPDATED_SUCCESS.getMessage()); + NoticeDTO.SaveResponse response = noticeUsecase.update(noticeId, dto, userId); + + return CommonResponse.createSuccess(NOTICE_UPDATED_SUCCESS.getMessage(), response); } @DeleteMapping("/{noticeId}") diff --git a/src/main/java/leets/weeth/domain/board/presentation/PostController.java b/src/main/java/leets/weeth/domain/board/presentation/PostController.java index 05714ed6..6bfbf45b 100644 --- a/src/main/java/leets/weeth/domain/board/presentation/PostController.java +++ b/src/main/java/leets/weeth/domain/board/presentation/PostController.java @@ -1,15 +1,5 @@ package leets.weeth.domain.board.presentation; -import static leets.weeth.domain.board.presentation.ResponseMessage.EDUCATION_SEARCH_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_CREATED_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_DELETED_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_EDU_FIND_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_FIND_ALL_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_FIND_BY_ID_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_PART_FIND_ALL_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_SEARCH_SUCCESS; -import static leets.weeth.domain.board.presentation.ResponseMessage.POST_UPDATED_SUCCESS; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -23,16 +13,9 @@ import leets.weeth.global.common.response.CommonResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Slice; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import static leets.weeth.domain.board.presentation.ResponseMessage.*; @Tag(name = "BOARD", description = "게시판 API") @RestController @@ -44,10 +27,10 @@ public class PostController { @PostMapping @Operation(summary="파트 게시글 생성 (스터디 로그, 아티클)") - public CommonResponse save(@RequestBody @Valid PostDTO.Save dto, @Parameter(hidden = true) @CurrentUser Long userId) { - postUsecase.save(dto, userId); + public CommonResponse save(@RequestBody @Valid PostDTO.Save dto, @Parameter(hidden = true) @CurrentUser Long userId) { + PostDTO.SaveResponse response = postUsecase.save(dto, userId); - return CommonResponse.createSuccess(POST_CREATED_SUCCESS.getMessage()); + return CommonResponse.createSuccess(POST_CREATED_SUCCESS.getMessage(), response); } @GetMapping @@ -101,11 +84,12 @@ public CommonResponse> findEducation(@Reques @PatchMapping(value = "/{boardId}/part") @Operation(summary="파트 게시글 수정") - public CommonResponse update(@PathVariable Long boardId, + public CommonResponse update(@PathVariable Long boardId, @RequestBody @Valid PostDTO.Update dto, @Parameter(hidden = true) @CurrentUser Long userId) throws UserNotMatchException { - postUsecase.update(boardId, dto, userId); - return CommonResponse.createSuccess(POST_UPDATED_SUCCESS.getMessage()); + PostDTO.SaveResponse response = postUsecase.update(boardId, dto, userId); + + return CommonResponse.createSuccess(POST_UPDATED_SUCCESS.getMessage(), response); } @DeleteMapping("/{boardId}") From e3a93a4eb2c8d4fc1e42c236d710c3f578e34b07 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 27 Oct 2025 22:05:44 +0900 Subject: [PATCH 048/114] =?UTF-8?q?test:=20mapper=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/mapper/PostMapperTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java diff --git a/src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java b/src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java new file mode 100644 index 00000000..ac51a581 --- /dev/null +++ b/src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java @@ -0,0 +1,56 @@ +package leets.weeth.domain.board.application.mapper; + +import leets.weeth.domain.board.application.dto.PostDTO; +import leets.weeth.domain.board.domain.entity.Post; +import leets.weeth.domain.user.domain.entity.User; +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.mapstruct.factory.Mappers; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class PostMapperTest { + + @InjectMocks + private PostMapper mapper = Mappers.getMapper(PostMapper.class); + + private User testUser; + private Post testPost; + + @BeforeEach + void setUp() { + testUser = User.builder() + .id(1L) + .name("테스트유저") + .email("test@weeth.com") + .build(); + + testPost = Post.builder() + .id(1L) + .title("테스트 게시글") + .user(testUser) + .content("테스트 내용입니다.") + .user(testUser) + .build(); + } + + @Test + @DisplayName("Post를 PostDTO.SaveResponse로 변환") + void toSaveResponse() { + // given + // testPost 사용 + + // when + PostDTO.SaveResponse response = mapper.toSaveResponse(testPost); + + // then + assertThat(response).isNotNull(); + assertThat(response.id()).isEqualTo(testPost.getId()); + } + +} From 7c667ba5fbe069f9dc3efe397730220a61e729eb Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 27 Oct 2025 22:15:31 +0900 Subject: [PATCH 049/114] =?UTF-8?q?docs:=20pr=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/pull_request_template.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0b2ba913..7a3738b5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,23 +1,23 @@ -## PR 내용 +# 📌 PR 내용
-## PR 세부사항 +## 🔍 PR 세부사항
-## 관련 스크린샷 +## 📸 관련 스크린샷
-## 주의사항 +## 📝 주의사항
-## 체크 리스트 +## ✅ 체크리스트 - [ ] 리뷰어 설정 - [ ] Assignee 설정 - [ ] Label 설정 -- [ ] 제목 양식 맞췄나요? (ex. #0 Feat: 기능 추가) -- [ ] 변경 사항에 대한 테스트 \ No newline at end of file +- [ ] 제목 양식 맞췄나요? (ex. [WTH-01] PR 템플릿 수정) +- [ ] 변경 사항에 대한 테스트 From 0a6241aae7b7385836002f85e3a6433636e3f526 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 28 Oct 2025 19:55:46 +0900 Subject: [PATCH 050/114] =?UTF-8?q?=20feat=20:=20UserTestFixture=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/test/fixture/UserTestFixture.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java diff --git a/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java b/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java new file mode 100644 index 00000000..4a42a4f9 --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java @@ -0,0 +1,43 @@ +package leets.weeth.domain.user.test.fixture; + +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Status; + +public class UserTestFixture { + + public static User createActiveUser() { + return User.builder() + .name("적순") + .email("test1@test.com") + .status(Status.ACTIVE) + .build(); + } + + public static User createActiveUser(Long id) { + return User.builder() + .id(id) + .name("적순") + .email("test1@test.com") + .status(Status.ACTIVE) + .build(); + } + + public static User createWaitingUser() { + return User.builder() + .name("순적") + .email("test2@test.com") + .status(Status.WAITING) + .build(); + } + + public static User createWaitingUser(Long id) { + return User.builder() + .id(id) + .name("순적") + .email("test2@test.com") + .status(Status.WAITING) + .build(); + } + + +} From 053a515ab33066c245262b0e251cc8f85c3f50a4 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 28 Oct 2025 20:00:53 +0900 Subject: [PATCH 051/114] =?UTF-8?q?=20feat=20:=20CardinalTestFixture=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/fixture/CardinalTestFixture.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java diff --git a/src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java b/src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java new file mode 100644 index 00000000..45bdd534 --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java @@ -0,0 +1,45 @@ +package leets.weeth.domain.user.test.fixture; + +import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.entity.enums.CardinalStatus; + +public class CardinalTestFixture { + + public static Cardinal createCardinal() { + return Cardinal.builder() + .cardinalNumber(3) + .year(2024) + .semester(1) + .status(CardinalStatus.DONE) + .build(); + } + + public static Cardinal createCardinal(Long id) { + return Cardinal.builder() + .id(id) + .cardinalNumber(3) + .year(2024) + .semester(1) + .status(CardinalStatus.DONE) + .build(); + } + + public static Cardinal createCardinalInProgress() { + return Cardinal.builder() + .cardinalNumber(3) + .year(2024) + .semester(1) + .status(CardinalStatus.IN_PROGRESS) + .build(); + } + + public static Cardinal createCardinalInProgress(Long id) { + return Cardinal.builder() + .id(id) + .cardinalNumber(3) + .year(2024) + .semester(1) + .status(CardinalStatus.IN_PROGRESS) + .build(); + } +} From 85ddcfda35957a7e778bcb69acc9094e398cbced Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 28 Oct 2025 20:03:45 +0900 Subject: [PATCH 052/114] =?UTF-8?q?=20feat=20:=20UserCardinalTestFixture?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/test/fixture/UserCardinalTestFixture.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/test/fixture/UserCardinalTestFixture.java diff --git a/src/test/java/leets/weeth/domain/user/test/fixture/UserCardinalTestFixture.java b/src/test/java/leets/weeth/domain/user/test/fixture/UserCardinalTestFixture.java new file mode 100644 index 00000000..686031da --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/test/fixture/UserCardinalTestFixture.java @@ -0,0 +1,12 @@ +package leets.weeth.domain.user.test.fixture; + +import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.UserCardinal; + +public class UserCardinalTestFixture { + + public static UserCardinal linkUserCardinal(User user, Cardinal cardinal) { + return new UserCardinal(user, cardinal); + } +} From bd9e1cfae6deed48e954d82f80ec62eb2ffee929 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 28 Oct 2025 20:20:01 +0900 Subject: [PATCH 053/114] =?UTF-8?q?=20fix=20:=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=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 --- .../test/fixture/CardinalTestFixture.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java b/src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java index 45bdd534..ecb713f4 100644 --- a/src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java +++ b/src/test/java/leets/weeth/domain/user/test/fixture/CardinalTestFixture.java @@ -5,40 +5,40 @@ public class CardinalTestFixture { - public static Cardinal createCardinal() { + public static Cardinal createCardinal(int cardinalNumber, int year, int semester) { return Cardinal.builder() - .cardinalNumber(3) - .year(2024) - .semester(1) + .cardinalNumber(cardinalNumber) + .year(year) + .semester(semester) .status(CardinalStatus.DONE) .build(); } - public static Cardinal createCardinal(Long id) { + public static Cardinal createCardinal(Long id, int cardinalNumber, int year, int semester) { return Cardinal.builder() .id(id) - .cardinalNumber(3) - .year(2024) - .semester(1) + .cardinalNumber(cardinalNumber) + .year(year) + .semester(semester) .status(CardinalStatus.DONE) .build(); } - public static Cardinal createCardinalInProgress() { + public static Cardinal createCardinalInProgress ( int cardinalNumber, int year, int semester) { return Cardinal.builder() - .cardinalNumber(3) - .year(2024) - .semester(1) + .cardinalNumber(cardinalNumber) + .year(year) + .semester(semester) .status(CardinalStatus.IN_PROGRESS) .build(); } - public static Cardinal createCardinalInProgress(Long id) { + public static Cardinal createCardinalInProgress(Long id, int cardinalNumber, int year, int semester) { return Cardinal.builder() .id(id) - .cardinalNumber(3) - .year(2024) - .semester(1) + .cardinalNumber(cardinalNumber) + .year(year) + .semester(semester) .status(CardinalStatus.IN_PROGRESS) .build(); } From c657270db194de369529382d4ff7c5542621f1e1 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 28 Oct 2025 20:20:34 +0900 Subject: [PATCH 054/114] =?UTF-8?q?=20refactor=20:=20CardinalTestFixture?= =?UTF-8?q?=20=EA=B8=B0=EB=B0=98=20CardinalUseCaseTest=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/CardinalUseCaseTest.java | 63 +++---------------- 1 file changed, 9 insertions(+), 54 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java index 6b4fa81e..316de92c 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/CardinalUseCaseTest.java @@ -22,6 +22,7 @@ import leets.weeth.domain.user.domain.entity.enums.CardinalStatus; import leets.weeth.domain.user.domain.service.CardinalGetService; import leets.weeth.domain.user.domain.service.CardinalSaveService; +import leets.weeth.domain.user.test.fixture.CardinalTestFixture; @ExtendWith(MockitoExtension.class) public class CardinalUseCaseTest { @@ -46,18 +47,8 @@ public class CardinalUseCaseTest { //given var request = new CardinalSaveRequest(7, 2025,1,false); - var toSave = Cardinal.builder() //DB저장되기 전의 객체 - .cardinalNumber(7) - .year(2025) - .semester(1) - .build(); - - var saved = Cardinal.builder()// 저장되고 난 후 반환된 객체 - .cardinalNumber(7) - .year(2025) - .semester(1) - .status(CardinalStatus.DONE) - .build(); + var toSave = CardinalTestFixture.createCardinal(7, 2025, 1); + var saved = CardinalTestFixture.createCardinal(7,2025 ,1); willDoNothing().given(cardinalGetService).validateCardinal(7); given(cardinalMapper.from(request)).willReturn(toSave); @@ -77,27 +68,9 @@ public class CardinalUseCaseTest { // given var request = new CardinalSaveRequest(7, 2025,1,true); - var oldCardinal = Cardinal.builder() - .cardinalNumber(6) - .year(2024) - .semester(2) - .status(CardinalStatus.IN_PROGRESS) - .build(); - - var newCardinalBeforeSave = Cardinal.builder() - .cardinalNumber(7) - .year(2025) - .semester(1) - .status(CardinalStatus.DONE) - .build(); - - var newCardinalAfterSave = Cardinal.builder() - .cardinalNumber(7) - .year(2025) - .semester(1) - .status(CardinalStatus.IN_PROGRESS) - .build(); - + var oldCardinal = CardinalTestFixture.createCardinalInProgress(6, 2024, 2); + var newCardinalBeforeSave = CardinalTestFixture.createCardinal(7, 2025, 1); + var newCardinalAfterSave = CardinalTestFixture.createCardinal(7, 2025, 1); given(cardinalGetService.findInProgress()).willReturn(List.of(oldCardinal)); given(cardinalMapper.from(request)).willReturn(newCardinalBeforeSave); @@ -118,10 +91,7 @@ public class CardinalUseCaseTest { @Test void update_연도와_학기를_변경한다() { //given - var cardinal = Cardinal.builder() - .year(2024) - .semester(2) - .build(); + var cardinal = CardinalTestFixture.createCardinal(6, 2024, 2); var dto = new CardinalUpdateRequest(1L, 2025,1,false); //when @@ -137,24 +107,9 @@ public class CardinalUseCaseTest { void findAll_조회된_모든_기수를_DTO로_매핑처리() { //given - var cardinal1 = Cardinal.builder() - .id(1L) - .cardinalNumber(6) - .year(2024) - .semester(2) - .status(CardinalStatus.DONE) - .build(); - - var cardinal2 = Cardinal.builder() - .id(2L) - .cardinalNumber(7) - .year(2025) - .semester(1) - .status(CardinalStatus.IN_PROGRESS) - .build(); - + var cardinal1 = CardinalTestFixture.createCardinal(1L,6,2024,2); + var cardinal2 = CardinalTestFixture.createCardinalInProgress(2L,7,2025,1); var cardinals = List.of(cardinal1, cardinal2); - var now = LocalDateTime.now(); var response1 = new CardinalResponse( From 2ab18783c827da28bfac2d189d7b41adec48e970 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 28 Oct 2025 20:45:33 +0900 Subject: [PATCH 055/114] =?UTF-8?q?add=20:=20=EC=9C=A0=EC=A0=802=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/test/fixture/UserTestFixture.java | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java b/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java index 4a42a4f9..d56697f2 100644 --- a/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java +++ b/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java @@ -5,7 +5,7 @@ public class UserTestFixture { - public static User createActiveUser() { + public static User createActiveUser1() { return User.builder() .name("적순") .email("test1@test.com") @@ -13,7 +13,7 @@ public static User createActiveUser() { .build(); } - public static User createActiveUser(Long id) { + public static User createActiveUser1(Long id) { return User.builder() .id(id) .name("적순") @@ -22,7 +22,24 @@ public static User createActiveUser(Long id) { .build(); } - public static User createWaitingUser() { + public static User createActiveUser2() { + return User.builder() + .name("적순2") + .email("test2@test.com") + .status(Status.ACTIVE) + .build(); + } + + public static User createActiveUser2(Long id) { + return User.builder() + .id(id) + .name("적순") + .email("test2@test.com") + .status(Status.ACTIVE) + .build(); + } + + public static User createWaitingUser1() { return User.builder() .name("순적") .email("test2@test.com") @@ -30,7 +47,7 @@ public static User createWaitingUser() { .build(); } - public static User createWaitingUser(Long id) { + public static User createWaitingUser1(Long id) { return User.builder() .id(id) .name("순적") @@ -39,5 +56,21 @@ public static User createWaitingUser(Long id) { .build(); } + public static User createWaitingUser2() { + return User.builder() + .name("순적2") + .email("test3@test.com") + .status(Status.WAITING) + .build(); + } + + public static User createWaitingUser2(Long id) { + return User.builder() + .id(id) + .name("순적2") + .email("test3@test.com") + .status(Status.WAITING) + .build(); + } } From 875602986e2ec21b3a672a66fae7a7ee681e9047 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 28 Oct 2025 20:46:26 +0900 Subject: [PATCH 056/114] =?UTF-8?q?=20refactor=20:=20=20Fixture=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/UserManageUseCaseTest.java | 85 ++++--------------- 1 file changed, 15 insertions(+), 70 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index f40f7976..391053ce 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -36,6 +36,8 @@ import leets.weeth.domain.user.domain.service.UserDeleteService; import leets.weeth.domain.user.domain.service.UserGetService; import leets.weeth.domain.user.domain.service.UserUpdateService; +import leets.weeth.domain.user.test.fixture.CardinalTestFixture; +import leets.weeth.domain.user.test.fixture.UserTestFixture; import leets.weeth.global.auth.jwt.service.JwtRedisService; @ExtendWith(MockitoExtension.class) @@ -75,32 +77,10 @@ public class UserManageUseCaseTest { //given UsersOrderBy orderBy = UsersOrderBy.NAME_ASCENDING; - var user1 = User.builder() - .id(1L) - .name("aaa") - .status(Status.ACTIVE) - .build(); - - var user2 = User.builder() - .id(2L) - .name("bbb") - .status(Status.WAITING) - .build(); - - var cd1 = Cardinal.builder() - .id(1L) - .cardinalNumber(6) - .year(2020) - .semester(2) - .build(); - - var cd2 = Cardinal.builder() - .id(2L) - .cardinalNumber(7) - .year(2021) - .semester(1) - .build(); - + var user1 = UserTestFixture.createActiveUser1(); + var user2 = UserTestFixture.createWaitingUser2(); + var cd1 = CardinalTestFixture.createCardinal(1L,6,2020,2); + var cd2 = CardinalTestFixture.createCardinal(2L,7,2021,1); var uc1 = new UserCardinal(user1, cd1); var uc2 = new UserCardinal(user2, cd2); @@ -120,8 +100,6 @@ public class UserManageUseCaseTest { LocalDateTime.now() ); - - given(userCardinalGetService.getUserCardinals(user1)).willReturn(List.of(uc1)); given(userCardinalGetService.getUserCardinals(user2)).willReturn(List.of(uc2)); given(userCardinalGetService.findAll()).willReturn(List.of(uc1, uc2)); @@ -144,16 +122,9 @@ public class UserManageUseCaseTest { @Test void accept_비활성유저_승인시_출석초기화_정상호출되는지() { //given - var user1 = User.builder() - .id(1L) - .name("aaa") - .status(Status.WAITING) - .build(); + var user1 = UserTestFixture.createWaitingUser1(1L); var userIds = new UserRequestDto.UserId(List.of(1L)); - var cardinal = Cardinal.builder() - .id(1L) - .cardinalNumber(8) - .build(); + var cardinal = CardinalTestFixture.createCardinal(1L,8,2020,2); var meetings = List.of(mock(Meeting.class)); given(userGetService.findAll(userIds.userId())).willReturn(List.of(user1)); @@ -172,7 +143,7 @@ public class UserManageUseCaseTest { @Test void update_유저권한변경시_DB와_Redis_모두갱신되는지() { // given - var user1 = User.builder().id(1L).build(); + var user1 = UserTestFixture.createActiveUser1(1L); var request = new UserRequestDto.UserRoleUpdate(1L, Role.ADMIN); lenient().when(userGetService.find((Long)1L)).thenReturn(user1); @@ -188,10 +159,7 @@ public class UserManageUseCaseTest { @Test void leave_회원탈퇴시_토큰무효화_및_유저상태변경되는지() { //given - var user1 = User.builder(). - id(1L) - .status(Status.ACTIVE) - .build(); + var user1 = UserTestFixture.createActiveUser1(1L); given(userGetService.find((Long)1L)).willReturn(user1); //when @@ -206,11 +174,7 @@ public class UserManageUseCaseTest { @Test void ban_회원ban시_토큰무효화_및_유저상태변경되는지() { //given - var user1 = User.builder(). - id(1L) - .status(Status.ACTIVE) - .build(); - + var user1 = UserTestFixture.createActiveUser1(1L); var ids = new UserRequestDto.UserId(List.of(1L)); given(userGetService.findAll(ids.userId())).willReturn(List.of(user1)); @@ -226,17 +190,8 @@ public class UserManageUseCaseTest { @Test void applyOB_현재기수_OB신청시_출석초기화_및_기수업데이트() { //given - var user = User.builder() - .id(1L) - .status(Status.ACTIVE) - .attendances(new ArrayList<>()) - .build(); - - var nextCardinal = Cardinal.builder() - .id(1L) - .cardinalNumber(4) - .build(); - + var user = User.builder().id(1L).name("aaa").status(Status.ACTIVE).attendances(new ArrayList<>()).build(); + var nextCardinal = CardinalTestFixture.createCardinal(1L,4,2020,2); var request = new UserRequestDto.UserApplyOB(1L,4); var meeting = List.of(mock(Meeting.class)); @@ -257,18 +212,8 @@ public class UserManageUseCaseTest { @Test void reset_비밀번호초기화시_모든유저에_reset호출되는지() { // given - var user1 = User.builder() - .id(1L) - .name("aaa") - .status(Status.ACTIVE) - .build(); - - var user2 = User.builder() - .id(2L) - .name("bbb") - .status(Status.ACTIVE) - .build(); - + var user1 = UserTestFixture.createActiveUser1(1L); + var user2 = UserTestFixture.createActiveUser2(2L); var ids = new UserRequestDto.UserId(List.of(1L, 2L)); given(userGetService.findAll(ids.userId())).willReturn(List.of(user1, user2)); From 79e766a1c12b2479fdf92ff4ffd6d1d234d7e017 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 29 Oct 2025 02:16:48 +0900 Subject: [PATCH 057/114] =?UTF-8?q?refactor=20:=20Repository=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20Fixture=EB=A1=9C=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CardinalRepositoryTest.java | 8 +--- .../UsesrCardinalRepositoryTest.java | 44 +++++++------------ 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java index c4307406..b773712d 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/CardinalRepositoryTest.java @@ -10,6 +10,7 @@ import leets.weeth.config.TestContainersConfig; import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.test.fixture.CardinalTestFixture; @DataJpaTest @Import(TestContainersConfig.class) @@ -22,12 +23,7 @@ public class CardinalRepositoryTest { @Test void 기수번호로_조회되는지() { //given - Cardinal cardinal = Cardinal.builder() - .id(1L) - .cardinalNumber(7) - .year(2025) - .semester(1) - .build(); + var cardinal = CardinalTestFixture.createCardinal(7,2025,1); cardinalRepository.save(cardinal); //when diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java index 08f66d97..78147584 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java @@ -16,6 +16,8 @@ import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.entity.UserCardinal; import leets.weeth.domain.user.domain.entity.enums.Status; +import leets.weeth.domain.user.test.fixture.CardinalTestFixture; +import leets.weeth.domain.user.test.fixture.UserTestFixture; @DataJpaTest @Import(TestContainersConfig.class) @@ -34,32 +36,13 @@ public class UsesrCardinalRepositoryTest { @Test void 유저별_기수_내림차순_조회되는지() { //given - User user = User.builder() - .email("test@test.com") - .name("문적순") - .status(Status.ACTIVE) - .build(); + var user = UserTestFixture.createActiveUser1(); userRepository.save(user); - Cardinal cardinal1 = cardinalRepository.save(Cardinal.builder() - .cardinalNumber(5) - .year(2023) - .semester(1) - .build()); - - Cardinal cardinal2 = cardinalRepository.save(Cardinal.builder() - .cardinalNumber(6) - .year(2023) - .semester(2) - .build()); - - Cardinal cardinal3 = cardinalRepository.save(Cardinal.builder() - .cardinalNumber(7) - .year(2024) - .semester(1) - .build()); - + var cardinal1 = cardinalRepository.save(CardinalTestFixture.createCardinal(5,2023,1)); + var cardinal2 = cardinalRepository.save(CardinalTestFixture.createCardinal(6,2023,2)); + var cardinal3 = cardinalRepository.save(CardinalTestFixture.createCardinal(7,2024,1)); userCardinalRepository.saveAll(List.of( new UserCardinal(user, cardinal1), @@ -81,13 +64,16 @@ public class UsesrCardinalRepositoryTest { @Test void 여러_유저의_기수를_유저별_내림차순으로_조회한다() { //given - User user1 = userRepository.save(User.builder().email("user1@test.com").name("적순").status(Status.ACTIVE).build()); - User user2 = userRepository.save(User.builder().email("user2@test.com").name("순적").status(Status.ACTIVE).build()); + var user1 = UserTestFixture.createActiveUser1(); + var user2 = UserTestFixture.createActiveUser2(); + + userRepository.save(user1); + userRepository.save(user2); - Cardinal c1 = cardinalRepository.save(Cardinal.builder().cardinalNumber(5).year(2023).semester(1).build()); - Cardinal c2 = cardinalRepository.save(Cardinal.builder().cardinalNumber(6).year(2023).semester(2).build()); - Cardinal c3 = cardinalRepository.save(Cardinal.builder().cardinalNumber(7).year(2024).semester(1).build()); - Cardinal c4 = cardinalRepository.save(Cardinal.builder().cardinalNumber(8).year(2024).semester(2).build()); + var c1 = cardinalRepository.save(CardinalTestFixture.createCardinal(5,2023,1)); + var c2 = cardinalRepository.save(CardinalTestFixture.createCardinal(6,2023,2)); + var c3 = cardinalRepository.save(CardinalTestFixture.createCardinal(7,2024,1)); + var c4 = cardinalRepository.save(CardinalTestFixture.createCardinal(8,2024,2)); userCardinalRepository.saveAll(List.of( new UserCardinal(user1, c3), From 717e0a7349db1daa9790a69f829802bd56896a96 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 29 Oct 2025 18:41:00 +0900 Subject: [PATCH 058/114] =?UTF-8?q?=20test=20:=20UserRepository=20Test=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/UserRepositoryTest.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java new file mode 100644 index 00000000..e9b7ce33 --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java @@ -0,0 +1,72 @@ +package leets.weeth.domain.user.domain.repository; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.*; + +import java.util.List; + +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.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import leets.weeth.config.TestContainersConfig; +import leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.UserCardinal; +import leets.weeth.domain.user.domain.entity.enums.Status; +import leets.weeth.domain.user.test.fixture.CardinalTestFixture; +import leets.weeth.domain.user.test.fixture.UserCardinalTestFixture; +import leets.weeth.domain.user.test.fixture.UserTestFixture; + +@DataJpaTest +@Import(TestContainersConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserCardinalRepository userCardinalRepository; + + @Autowired + private CardinalRepository cardinalRepository; + + private Cardinal cardinal7; + private Cardinal cardinal8; + + @BeforeEach + void setUp() { + + cardinal7 = cardinalRepository.save(CardinalTestFixture.createCardinal(7, 2026, 1)); + cardinal8 = cardinalRepository.save(CardinalTestFixture.createCardinal(8, 2026, 2)); + + var user1 = userRepository.save(UserTestFixture.createActiveUser1()); + var user2 = userRepository.save(UserTestFixture.createActiveUser2()); + var user3 = userRepository.save(UserTestFixture.createWaitingUser1()); + + user1.accept(); + user2.accept(); + + userCardinalRepository.save(UserCardinalTestFixture.linkUserCardinal(user1, cardinal7)); + userCardinalRepository.save(UserCardinalTestFixture.linkUserCardinal(user2, cardinal8)); + userCardinalRepository.save(UserCardinalTestFixture.linkUserCardinal(user3, cardinal7)); + userCardinalRepository.flush(); + } + + @Test + @DisplayName("findAllByCardinalAndStatus(): 특정 기수 + 상태에 맞는 유저만 조회된다") + void findAllByCardinalAndStatus() { + // when + List result = userRepository.findAllByCardinalAndStatus(cardinal7, Status.ACTIVE); + + // then + assertThat(result) + .hasSize(1) + .extracting(User::getName) + .containsExactly("적순"); + } +} From 63ec6cc6d07a29c0a757a1ce852a6494719f3838 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 29 Oct 2025 19:10:37 +0900 Subject: [PATCH 059/114] =?UTF-8?q?chore:=20management=20endpoints=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9D=84=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=98=95=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 --- src/main/resources/application.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 37cccbfe..82381f45 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -44,7 +44,9 @@ management: endpoints: web: exposure: - include: "health,prometheus" + include: + - health + - prometheus prometheus: metrics: export: From df6ebd2ceb9f7d1d8585b8d433f9ace6dbd03935 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Wed, 29 Oct 2025 19:51:31 +0900 Subject: [PATCH 060/114] =?UTF-8?q?test:=20=EC=A4=91=EB=B3=B5=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/domain/board/application/mapper/PostMapperTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java b/src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java index ac51a581..0f2f006c 100644 --- a/src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java +++ b/src/test/java/leets/weeth/domain/board/application/mapper/PostMapperTest.java @@ -35,7 +35,6 @@ void setUp() { .title("테스트 게시글") .user(testUser) .content("테스트 내용입니다.") - .user(testUser) .build(); } From 29d4d8dbdc6ab6eba91e575577e2e4d7e6fcced7 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 29 Oct 2025 20:03:37 +0900 Subject: [PATCH 061/114] =?UTF-8?q?=20fix=20:=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...alRepositoryTest.java => UserCardinalRepositoryTest.java} | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) rename src/test/java/leets/weeth/domain/user/domain/repository/{UsesrCardinalRepositoryTest.java => UserCardinalRepositoryTest.java} (94%) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UserCardinalRepositoryTest.java similarity index 94% rename from src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java rename to src/test/java/leets/weeth/domain/user/domain/repository/UserCardinalRepositoryTest.java index 78147584..34a9bafd 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UsesrCardinalRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UserCardinalRepositoryTest.java @@ -12,17 +12,14 @@ import org.springframework.context.annotation.Import; import leets.weeth.config.TestContainersConfig; -import leets.weeth.domain.user.domain.entity.Cardinal; -import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.entity.UserCardinal; -import leets.weeth.domain.user.domain.entity.enums.Status; import leets.weeth.domain.user.test.fixture.CardinalTestFixture; import leets.weeth.domain.user.test.fixture.UserTestFixture; @DataJpaTest @Import(TestContainersConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -public class UsesrCardinalRepositoryTest { +public class UserCardinalRepositoryTest { @Autowired UserRepository userRepository; From 86c9de37dba8d585551f02c19e23c3fc8f3d3142 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 29 Oct 2025 20:06:25 +0900 Subject: [PATCH 062/114] =?UTF-8?q?=20fix=20:=20findAllByAdmin=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=9C=EC=84=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/user/application/usecase/UserManageUseCaseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java index 391053ce..0ab1e56d 100644 --- a/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java +++ b/src/test/java/leets/weeth/domain/user/application/usecase/UserManageUseCaseTest.java @@ -102,7 +102,7 @@ public class UserManageUseCaseTest { given(userCardinalGetService.getUserCardinals(user1)).willReturn(List.of(uc1)); given(userCardinalGetService.getUserCardinals(user2)).willReturn(List.of(uc2)); - given(userCardinalGetService.findAll()).willReturn(List.of(uc1, uc2)); + given(userCardinalGetService.findAll()).willReturn(List.of(uc2, uc1)); given(userMapper.toAdminResponse(user1, List.of(uc1))).willReturn(adminResponse1); given(userMapper.toAdminResponse(user2, List.of(uc2))).willReturn(adminResponse2); From 45e087b185aaae9583f65eadfb037af0b401d57c Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 29 Oct 2025 20:09:52 +0900 Subject: [PATCH 063/114] =?UTF-8?q?=20fix=20:=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=A0=81=EC=9A=A9=20flush=20=EC=9C=84?= =?UTF-8?q?=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 --- .../domain/user/domain/repository/UserRepositoryTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java index e9b7ce33..f0b3642e 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java @@ -50,11 +50,12 @@ void setUp() { user1.accept(); user2.accept(); + userCardinalRepository.flush(); userCardinalRepository.save(UserCardinalTestFixture.linkUserCardinal(user1, cardinal7)); userCardinalRepository.save(UserCardinalTestFixture.linkUserCardinal(user2, cardinal8)); userCardinalRepository.save(UserCardinalTestFixture.linkUserCardinal(user3, cardinal7)); - userCardinalRepository.flush(); + } @Test From ba8bf7097a5c8428f6b0aedec9c920b88f04f5b4 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 29 Oct 2025 20:18:55 +0900 Subject: [PATCH 064/114] =?UTF-8?q?=20fix=20:=20=20Fixture=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B6=88=EC=9D=BC=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leets/weeth/domain/user/test/fixture/UserTestFixture.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java b/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java index d56697f2..20009210 100644 --- a/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java +++ b/src/test/java/leets/weeth/domain/user/test/fixture/UserTestFixture.java @@ -33,7 +33,7 @@ public static User createActiveUser2() { public static User createActiveUser2(Long id) { return User.builder() .id(id) - .name("적순") + .name("적순2") .email("test2@test.com") .status(Status.ACTIVE) .build(); From 177c272c45ec762e20841771ca1a6ed8c33a4385 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Fri, 31 Oct 2025 09:45:35 +0900 Subject: [PATCH 065/114] =?UTF-8?q?test=20:=20find=5FtodayMeeting=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C,=20=EC=97=AC=EB=9F=AC=20=EC=B6=9C?= =?UTF-8?q?=EC=84=9D=20=EA=B0=9D=EC=B2=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AttendanceUseCaseImplTest.java | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java b/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java index ff757b50..d4c25ef7 100644 --- a/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java +++ b/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java @@ -45,24 +45,57 @@ public class AttendanceUseCaseImplTest { @InjectMocks private AttendanceUseCaseImpl attendanceUseCase; @Test - @DisplayName("find: 오늘 날짜의 정기모임이 있으면 해당 정기모임으로 Main 매핑") - void find_todayMeeting() { + @DisplayName("find: 여러 날짜의 출석 목록 중 '시작/종료 날짜가 모두 오늘'인 출석정보를 선택") + void find_todayMeeting_filtersDispersedAttendances() { // given LocalDate today = LocalDate.now(); - Meeting todayMeeting = createOneDayMeeting(today, 1, 1111, "Today Meeting"); - User user = createActiveUserWithAttendances("이지훈", List.of(todayMeeting)); - Attendance todayAttendance = user.getAttendances().get(0); + + Meeting yesterdayMeeting = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday"); + Meeting todayMeeting = createOneDayMeeting(today, 1, 2222, "Today"); + Meeting tomorrowMeeting = createOneDayMeeting(today.plusDays(1),1, 3333, "Tomorrow"); + + User user = createActiveUserWithAttendances("이지훈", + List.of(yesterdayMeeting, todayMeeting, tomorrowMeeting)); + + Attendance expectedTodayAttendance = user.getAttendances().stream() + .filter(a -> a.getMeeting().getTitle().equals("Today")) + .findFirst() + .orElseThrow(); + + when(userGetService.find(userId)).thenReturn(user); + AttendanceDTO.Main mapped = mock(AttendanceDTO.Main.class); + when(attendanceMapper.toMainDto(user, expectedTodayAttendance)).thenReturn(mapped); + + // when + AttendanceDTO.Main actual = attendanceUseCase.find(userId); + + // then + assertThat(actual).isSameAs(mapped); + verify(attendanceMapper).toMainDto(user, expectedTodayAttendance); + } + + @Test + @DisplayName("find: '시작/종료 날짜가 모두 오늘'인 출석이 없다면, mapper.toMainDto(user, null)을 호출") + void find_noExactToday_returnsNullMapped() { + // given + LocalDate today = LocalDate.now(); + + Meeting yesterdayMeeting = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday"); + Meeting tomorrowMeeting = createOneDayMeeting(today.plusDays(1), 1, 3333, "Tomorrow"); + + User user = createActiveUserWithAttendances("이지훈", + List.of(yesterdayMeeting, tomorrowMeeting)); when(userGetService.find(userId)).thenReturn(user); - when(attendanceMapper.toMainDto(eq(user), eq(todayAttendance))) - .thenReturn(mock(AttendanceDTO.Main.class)); + AttendanceDTO.Main mapped = mock(AttendanceDTO.Main.class); + when(attendanceMapper.toMainDto(user, null)).thenReturn(mapped); // when AttendanceDTO.Main actual = attendanceUseCase.find(userId); // then - assertThat(actual).isNotNull(); - verify(attendanceMapper).toMainDto(eq(user), eq(todayAttendance)); + assertThat(actual).isSameAs(mapped); + verify(attendanceMapper).toMainDto(user, null); } @Test From 1e2faa5b0162f0fdcc0a4997adc9e24452167933 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Fri, 31 Oct 2025 10:28:17 +0900 Subject: [PATCH 066/114] =?UTF-8?q?test=20:=20DTO=20=EB=A7=A4=ED=95=91=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=EC=9D=80=20MapperTest=EB=A1=9C=20=EC=B1=85?= =?UTF-8?q?=EC=9E=84=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/AttendanceMapperTest.java | 124 ++++++++++++++++++ .../AttendanceUseCaseImplTest.java | 25 +--- .../test/fixture/AttendanceTestFixture.java | 45 +++++++ 3 files changed, 170 insertions(+), 24 deletions(-) create mode 100644 src/test/java/leets/weeth/domain/attendance/application/mapper/AttendanceMapperTest.java rename src/test/java/leets/weeth/domain/attendance/application/{ => usecase}/AttendanceUseCaseImplTest.java (89%) diff --git a/src/test/java/leets/weeth/domain/attendance/application/mapper/AttendanceMapperTest.java b/src/test/java/leets/weeth/domain/attendance/application/mapper/AttendanceMapperTest.java new file mode 100644 index 00000000..20578fee --- /dev/null +++ b/src/test/java/leets/weeth/domain/attendance/application/mapper/AttendanceMapperTest.java @@ -0,0 +1,124 @@ +package leets.weeth.domain.attendance.application.mapper; + +import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import leets.weeth.domain.attendance.application.dto.AttendanceDTO; +import leets.weeth.domain.attendance.domain.entity.Attendance; +import leets.weeth.domain.schedule.domain.entity.Meeting; +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Position; + +class AttendanceMapperTest { + + private final AttendanceMapper attendanceMapper = new AttendanceMapperImpl(); + + @Test + @DisplayName("toMainDto: 사용자 + 당일 출석 객체를 Main DTO로 매핑한다") + void toMainDto_mapsUserAndTodayAttendance() { + // given + LocalDate today = LocalDate.now(); + Meeting meeting = createOneDayMeeting(today, 1, 1111, "Today"); + User user = createActiveUserWithAttendances("이지훈", List.of(meeting)); + Attendance attendance = user.getAttendances().get(0); + + // when + AttendanceDTO.Main main = attendanceMapper.toMainDto(user, attendance); + + // then + assertThat(main).isNotNull(); + assertThat(main.title()).isEqualTo("Today"); + assertThat(main.status()).isEqualTo(attendance.getStatus()); + assertThat(main.start()).isEqualTo(meeting.getStart()); + assertThat(main.end()).isEqualTo(meeting.getEnd()); + assertThat(main.location()).isEqualTo(meeting.getLocation()); + } + + @Test + @DisplayName("toResponseDto: 단일 출석을 Response DTO로 매핑한다") + void toResponseDto_mapsSingleAttendance() { + // given + Meeting meeting = createOneDayMeeting(LocalDate.now().minusDays(1), 1, 2222, "D-1"); + User user = createActiveUser("사용자A"); + Attendance attendance = createAttendance(meeting, user); + + // when + AttendanceDTO.Response response = attendanceMapper.toResponseDto(attendance); + + // then + assertThat(response).isNotNull(); + assertThat(response.title()).isEqualTo("D-1"); + assertThat(response.start()).isEqualTo(meeting.getStart()); + assertThat(response.end()).isEqualTo(meeting.getEnd()); + assertThat(response.location()).isEqualTo(meeting.getLocation()); + } + + @Test + @DisplayName("toDetailDto: 사용자 + Response 리스트를 Detail DTO로 매핑(total = attend + absence)") + void toDetailDto_mapsDetailAndTotal() { + // given + LocalDate base = LocalDate.now(); + Meeting m1 = createOneDayMeeting(base.minusDays(2), 1, 1000, "D-2"); + Meeting m2 = createOneDayMeeting(base.minusDays(1), 1, 1001, "D-1"); + User user = createActiveUser("이지훈"); + setUserAttendanceStats(user, 3, 2); + + Attendance a1 = createAttendance(m1, user); + Attendance a2 = createAttendance(m2, user); + + AttendanceDTO.Response r1 = attendanceMapper.toResponseDto(a1); + AttendanceDTO.Response r2 = attendanceMapper.toResponseDto(a2); + + // when + AttendanceDTO.Detail detail = attendanceMapper.toDetailDto(user, List.of(r1, r2)); + + // then + assertThat(detail).isNotNull(); + assertThat(detail.attendances()).hasSize(2); + assertThat(detail.total()).isEqualTo(5); + } + + @Test + @DisplayName("toAttendanceInfoDto: Attendance를 Info DTO로 매핑") + void toAttendanceInfoDto_mapsInfo() { + // given + Meeting meeting = createOneDayMeeting(LocalDate.now(), 1, 3333, "Info"); + User user = createActiveUser("유저B"); + enrichUserProfile(user, Position.BE, "컴퓨터공학과", "20201234"); + + Attendance attendance = createAttendance(meeting, user); + setAttendanceId(attendance, 10L); + + // when + AttendanceDTO.AttendanceInfo info = attendanceMapper.toAttendanceInfoDto(attendance); + + // then + assertThat(info).isNotNull(); + assertThat(info.id()).isEqualTo(10L); + assertThat(info.status()).isEqualTo(attendance.getStatus()); + assertThat(info.name()).isEqualTo("유저B"); + } + + @Test + @DisplayName("null 안전성 테스트: todayAttendance가 null이면 필드는 null로 매핑") + void nullSafety_whenTodayAttendanceNull() { + // given + User user = createActiveUser("이지훈"); + + // when + AttendanceDTO.Main main = attendanceMapper.toMainDto(user, null); + + // then + assertThat(main).isNotNull(); + assertThat(main.title()).isNull(); + assertThat(main.start()).isNull(); + assertThat(main.end()).isNull(); + assertThat(main.location()).isNull(); + } +} \ No newline at end of file diff --git a/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java similarity index 89% rename from src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java rename to src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java index d4c25ef7..047949ee 100644 --- a/src/test/java/leets/weeth/domain/attendance/application/AttendanceUseCaseImplTest.java +++ b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java @@ -1,4 +1,4 @@ -package leets.weeth.domain.attendance.application; +package leets.weeth.domain.attendance.application.usecase; import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; import static org.assertj.core.api.Assertions.*; @@ -19,7 +19,6 @@ import leets.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; import leets.weeth.domain.attendance.application.exception.AttendanceNotFoundException; import leets.weeth.domain.attendance.application.mapper.AttendanceMapper; -import leets.weeth.domain.attendance.application.usecase.AttendanceUseCaseImpl; import leets.weeth.domain.attendance.domain.entity.Attendance; import leets.weeth.domain.attendance.domain.entity.enums.Status; import leets.weeth.domain.attendance.domain.service.AttendanceGetService; @@ -132,28 +131,6 @@ void findAllDetailsByCurrentCardinal() { verify(attendanceMapper).toDetailDto(eq(user), argThat(list -> list.size() == 2)); } - @Test - @DisplayName("findAllAttendanceByMeeting: 정기모임 조회 후 해당 출석들을 DTO로 매핑") - void findAllAttendanceByMeeting() { - // given - Meeting meeting = createOneDayMeeting(LocalDate.now(), 1, 1111, "Today"); - Attendance a1 = createAttendance(meeting, createActiveUser("A")); - Attendance a2 = createAttendance(meeting, createActiveUser("B")); - - when(meetingGetService.find(99L)).thenReturn(meeting); - when(attendanceGetService.findAllByMeeting(meeting)).thenReturn(List.of(a1, a2)); - AttendanceDTO.AttendanceInfo i1 = mock(AttendanceDTO.AttendanceInfo.class); - AttendanceDTO.AttendanceInfo i2 = mock(AttendanceDTO.AttendanceInfo.class); - when(attendanceMapper.toAttendanceInfoDto(a1)).thenReturn(i1); - when(attendanceMapper.toAttendanceInfoDto(a2)).thenReturn(i2); - - // when - List actual = attendanceUseCase.findAllAttendanceByMeeting(99L); - - // then - assertThat(actual).containsExactly(i1, i2); - } - @Test @DisplayName("close(now, cardinal): 당일 정기모임을 찾아 close") void close_success() { diff --git a/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java b/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java index c5989aae..91bce921 100644 --- a/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java +++ b/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java @@ -1,5 +1,6 @@ package leets.weeth.domain.attendance.test.fixture; +import java.lang.reflect.Field; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -7,6 +8,8 @@ import leets.weeth.domain.attendance.domain.entity.Attendance; import leets.weeth.domain.schedule.domain.entity.Meeting; import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.entity.enums.Department; +import leets.weeth.domain.user.domain.entity.enums.Position; import leets.weeth.domain.user.domain.entity.enums.Status; public class AttendanceTestFixture { @@ -63,4 +66,46 @@ public static Meeting createInProgressMeeting(int cardinal, int code, String tit .cardinal(cardinal) .build(); } + + private static void set(Object target, String fieldName, Object value) { + try { + Field f = target.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(target, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void setField(Object target, String fieldName, Object value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void setAttendanceId(Attendance attendance, Long id) { + setField(attendance, "id", id); + } + + public static void setUserAttendanceStats(User user, Integer attendanceCount, Integer absenceCount) { + setField(user, "attendanceCount", attendanceCount); + setField(user, "absenceCount", absenceCount); + } + + public static void enrichUserProfile(User user, Position position, Department department, String studentId) { + setField(user, "position", position); + setField(user, "department", department); + setField(user, "studentId", studentId); + } + + public static void enrichUserProfile(User user, Position position, String departmentKoreanValue, String studentId) { + setField(user, "position", position); + Department department = Department.to(departmentKoreanValue); // ← 핵심 + setField(user, "department", department); + setField(user, "studentId", studentId); + } } From 7e68933bfd618c4c41fb7ef95b9ab2f660649a70 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Fri, 31 Oct 2025 10:42:04 +0900 Subject: [PATCH 067/114] =?UTF-8?q?test=20:=20given/willReturn=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/AttendanceUseCaseImplTest.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java index 047949ee..c88766fb 100644 --- a/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java +++ b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java @@ -2,7 +2,7 @@ import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.mockito.BDDMockito.*; import java.time.LocalDate; import java.util.List; @@ -49,28 +49,31 @@ void find_todayMeeting_filtersDispersedAttendances() { // given LocalDate today = LocalDate.now(); - Meeting yesterdayMeeting = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday"); - Meeting todayMeeting = createOneDayMeeting(today, 1, 2222, "Today"); - Meeting tomorrowMeeting = createOneDayMeeting(today.plusDays(1),1, 3333, "Tomorrow"); + Meeting meetingYesterday = createOneDayMeeting(today.minusDays(1), 1, 1111, "Yesterday"); + Meeting meetingToday = createOneDayMeeting(today, 1, 2222, "Today"); + Meeting meetingTomorrow = createOneDayMeeting(today.plusDays(1),1, 3333, "Tomorrow"); - User user = createActiveUserWithAttendances("이지훈", - List.of(yesterdayMeeting, todayMeeting, tomorrowMeeting)); + User user = createActiveUserWithAttendances( + "이지훈", List.of(meetingYesterday, meetingToday, meetingTomorrow) + ); Attendance expectedTodayAttendance = user.getAttendances().stream() - .filter(a -> a.getMeeting().getTitle().equals("Today")) + .filter(attendance -> "Today".equals(attendance.getMeeting().getTitle())) .findFirst() .orElseThrow(); - when(userGetService.find(userId)).thenReturn(user); AttendanceDTO.Main mapped = mock(AttendanceDTO.Main.class); - when(attendanceMapper.toMainDto(user, expectedTodayAttendance)).thenReturn(mapped); + + given(userGetService.find(userId)).willReturn(user); + given(attendanceMapper.toMainDto(eq(user), eq(expectedTodayAttendance))).willReturn(mapped); // when AttendanceDTO.Main actual = attendanceUseCase.find(userId); // then assertThat(actual).isSameAs(mapped); - verify(attendanceMapper).toMainDto(user, expectedTodayAttendance); + then(attendanceMapper).should().toMainDto(eq(user), eq(expectedTodayAttendance)); + then(attendanceMapper).shouldHaveNoMoreInteractions(); } @Test From 6c592e4401945f523e1f84fc2b957225c8faa7a4 Mon Sep 17 00:00:00 2001 From: huncozyboy Date: Fri, 31 Oct 2025 10:46:18 +0900 Subject: [PATCH 068/114] =?UTF-8?q?test=20:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../attendance/test/fixture/AttendanceTestFixture.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java b/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java index 91bce921..6cfe52b8 100644 --- a/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java +++ b/src/test/java/leets/weeth/domain/attendance/test/fixture/AttendanceTestFixture.java @@ -67,16 +67,6 @@ public static Meeting createInProgressMeeting(int cardinal, int code, String tit .build(); } - private static void set(Object target, String fieldName, Object value) { - try { - Field f = target.getClass().getDeclaredField(fieldName); - f.setAccessible(true); - f.set(target, value); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - private static void setField(Object target, String fieldName, Object value) { try { Field field = target.getClass().getDeclaredField(fieldName); From 840fa5fba0e65b2cb157b1359544fa2346be91a6 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 15:22:22 +0900 Subject: [PATCH 069/114] =?UTF-8?q?style:=20=EA=B0=9C=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/application/usecase/NoticeUsecaseImplTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java index bee8048c..094fd75b 100644 --- a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -30,6 +30,7 @@ class NoticeUsecaseImplTest { @Mock private NoticeFindService noticeFindService; + @Mock private FileGetService fileGetService; From 3474b6a3aabd68e7b427172936a86625de05f3d9 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 15:24:39 +0900 Subject: [PATCH 070/114] refactor: given-willReturn --- .../usecase/NoticeUsecaseImplTest.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java index 094fd75b..83ba642f 100644 --- a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -11,6 +11,7 @@ import leets.weeth.domain.user.domain.entity.enums.Department; import leets.weeth.domain.user.domain.entity.enums.Position; import leets.weeth.domain.user.domain.entity.enums.Role; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -22,8 +23,8 @@ import java.util.ArrayList; import java.util.List; +import static org.mockito.BDDMockito.*; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; -import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class NoticeUsecaseImplTest { @@ -62,11 +63,11 @@ class NoticeUsecaseImplTest { Slice slice = new SliceImpl<>(List.of(notices.get(4), notices.get(3), notices.get(2)), pageable, true); - when(noticeFindService.findRecentNotices(any(Pageable.class))).thenReturn(slice); - when(fileGetService.findAllByNotice(any())).thenReturn(List.of()); + given(noticeFindService.findRecentNotices(any(Pageable.class))).willReturn(slice); + given(fileGetService.findAllByNotice(any())).willReturn(List.of()); - when(noticeMapper.toAll(any(Notice.class), anyBoolean())) - .thenAnswer(invocation -> { + given(noticeMapper.toAll(any(Notice.class), anyBoolean())) + .willAnswer(invocation -> { Notice notice = invocation.getArgument(0); return new NoticeDTO.ResponseAll( notice.getId(), @@ -122,10 +123,10 @@ class NoticeUsecaseImplTest { Slice slice = new SliceImpl<>(List.of(notices.get(5), notices.get(4), notices.get(3)), pageable, false); - when(noticeFindService.search(any(String.class), any(Pageable.class))).thenReturn(slice); + given(noticeFindService.search(any(String.class), any(Pageable.class))).willReturn(slice); // 짝수 id - 파일 존재, 홀수 id - 파일 없음 (빈 리스트) - when(fileGetService.findAllByNotice(any())) - .thenAnswer(invocation -> { + given(fileGetService.findAllByNotice(any())) + .willAnswer(invocation -> { Long noticeId = invocation.getArgument(0); if (noticeId % 2 == 0) { return List.of(File.builder() @@ -136,8 +137,8 @@ class NoticeUsecaseImplTest { } }); - when(noticeMapper.toAll(any(Notice.class), anyBoolean())) - .thenAnswer(invocation -> { + given(noticeMapper.toAll(any(Notice.class), anyBoolean())) + .willAnswer(invocation -> { Notice notice = invocation.getArgument(0); boolean fileExists = invocation.getArgument(1); return new NoticeDTO.ResponseAll( From c8ecb51bde33eb68e7b4e81de2dd86f2ecd8a24c Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 15:26:07 +0900 Subject: [PATCH 071/114] =?UTF-8?q?style:=20=EC=B6=94=ED=9B=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=ED=95=A0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EC=97=90=20@Disabled=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/application/usecase/NoticeUsecaseImplTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java index 83ba642f..863bc31b 100644 --- a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -170,10 +170,12 @@ class NoticeUsecaseImplTest { verify(noticeFindService, times(1)).search("검색", pageable); } + @Disabled("TODO: update 기능 테스트 구현 필요") @Test void update() { } + @Disabled("TODO: delete 기능 테스트 구현 필요") @Test void delete() { } From cb55f4bf84d87f5e4c003110b3142b14efae70e5 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 15:33:32 +0900 Subject: [PATCH 072/114] =?UTF-8?q?test:=20=EA=B3=B5=EC=A7=80=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=A0=84=EC=B2=B4=20=EC=88=9C=EC=84=9C=20=EA=B2=80?= =?UTF-8?q?=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 --- .../usecase/NoticeUsecaseImplTest.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java index 863bc31b..309f4f81 100644 --- a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -88,7 +88,13 @@ class NoticeUsecaseImplTest { // then assertThat(noticeResponses).isNotNull(); assertThat(noticeResponses.getContent()).hasSize(3); - assertThat(noticeResponses.getContent().get(0).title()).isEqualTo(notices.get(4).getTitle()); + assertThat(noticeResponses.getContent()) + .extracting(NoticeDTO.ResponseAll::title) + .containsExactly( + notices.get(4).getTitle(), + notices.get(3).getTitle(), + notices.get(2).getTitle() + ); assertThat(noticeResponses.hasNext()).isTrue(); verify(noticeFindService, times(1)).findRecentNotices(pageable); @@ -160,7 +166,13 @@ class NoticeUsecaseImplTest { // then assertThat(noticeResponses).isNotNull(); assertThat(noticeResponses.getContent()).hasSize(3); - assertThat(noticeResponses.getContent().get(0).title()).isEqualTo(notices.get(5).getTitle()); + assertThat(noticeResponses.getContent()) + .extracting(NoticeDTO.ResponseAll::title) + .containsExactly( + notices.get(5).getTitle(), + notices.get(4).getTitle(), + notices.get(3).getTitle() + ); assertThat(noticeResponses.hasNext()).isFalse(); // 짝수 id : 파일 존재, 홀수 id : 파일 없음 검증 From 2b940c5c584630dad19dada3e397bf35cd515a4e Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 15:57:35 +0900 Subject: [PATCH 073/114] =?UTF-8?q?test:=20NoticeRepository=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20UserRepository=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/NoticeRepositoryTest.java | 39 +++---------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java index 75c612f0..ccac1d40 100644 --- a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java @@ -2,12 +2,9 @@ import leets.weeth.config.TestContainersConfig; import leets.weeth.domain.board.domain.entity.Notice; -import leets.weeth.domain.user.domain.entity.User; -import leets.weeth.domain.user.domain.entity.enums.Department; -import leets.weeth.domain.user.domain.entity.enums.Position; -import leets.weeth.domain.user.domain.entity.enums.Role; -import leets.weeth.domain.user.domain.repository.UserRepository; +import leets.weeth.domain.user.domain.service.UserSaveService; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -19,57 +16,44 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - @DataJpaTest @Import(TestContainersConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class NoticeRepositoryTest { @Autowired - UserRepository userRepository; + private NoticeRepository noticeRepository; - @Autowired - NoticeRepository noticeRepository; + @InjectMocks + private UserSaveService userSaveService; @Test void findPageBy() { // given - User user = userRepository.save(User.builder() - .email("abc@test.com") - .name("홍길동") - .position(Position.BE) - .department(Department.SW) - .role(Role.USER) - .build()); Notice notice1 = noticeRepository.save(Notice.builder() .title("제목1") .content("내용1") - .user(user) .build()); Notice notice2 = noticeRepository.save(Notice.builder() .title("제목2") .content("내용2") - .user(user) .build()); Notice notice3 = noticeRepository.save(Notice.builder() .title("제목3") .content("내용3") - .user(user) .build()); Notice notice4 = noticeRepository.save(Notice.builder() .title("제목4") .content("내용4") - .user(user) .build()); Notice notice5 = noticeRepository.save(Notice.builder() .title("제목5") .content("내용5") - .user(user) .build()); Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "id")); @@ -88,42 +72,29 @@ void findPageBy() { @Test void search() { // given - User user = userRepository.save(User.builder() - .email("abc@test.com") - .name("홍길동") - .position(Position.BE) - .department(Department.SW) - .role(Role.USER) - .build()); - Notice notice1 = noticeRepository.save(Notice.builder() .title("검색1") .content("내용1") - .user(user) .build()); Notice notice2 = noticeRepository.save(Notice.builder() .title("제목2") .content("내용2") - .user(user) .build()); Notice notice3 = noticeRepository.save(Notice.builder() .title("검색3") .content("내용3") - .user(user) .build()); Notice notice4 = noticeRepository.save(Notice.builder() .title("제목4") .content("내용4") - .user(user) .build()); Notice notice5 = noticeRepository.save(Notice.builder() .title("검색5") .content("내용5") - .user(user) .build()); Pageable pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "id")); From 42be446fa2489e7ac3dd4b896872008ef9702b93 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 18:26:55 +0900 Subject: [PATCH 074/114] =?UTF-8?q?test:=20NoticeRepository=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20UserSaveService=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/board/domain/repository/NoticeRepositoryTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java index ccac1d40..fc10f5d4 100644 --- a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java @@ -24,13 +24,9 @@ class NoticeRepositoryTest { @Autowired private NoticeRepository noticeRepository; - @InjectMocks - private UserSaveService userSaveService; - @Test void findPageBy() { // given - Notice notice1 = noticeRepository.save(Notice.builder() .title("제목1") .content("내용1") From e017ec4f0b5cce961ff2d2f318a8085b2f8ffea1 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 18:36:23 +0900 Subject: [PATCH 075/114] =?UTF-8?q?test:=20NoticeFixture=EC=97=90=20user?= =?UTF-8?q?=20=EC=97=86=EC=9D=B4=20=EC=83=9D=EC=84=B1=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=9C=20createNotice=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=94=94=EB=A0=89=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/NoticeUsecaseImplTest.java | 2 +- .../board/domain/{ => test}/fixture/NoticeFixture.java | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) rename src/test/java/leets/weeth/domain/board/domain/{ => test}/fixture/NoticeFixture.java (59%) diff --git a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java index 309f4f81..f03100a9 100644 --- a/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java +++ b/src/test/java/leets/weeth/domain/board/application/usecase/NoticeUsecaseImplTest.java @@ -3,7 +3,7 @@ import leets.weeth.domain.board.application.dto.NoticeDTO; import leets.weeth.domain.board.application.mapper.NoticeMapper; import leets.weeth.domain.board.domain.entity.Notice; -import leets.weeth.domain.board.domain.fixture.NoticeFixture; +import leets.weeth.domain.board.domain.test.fixture.NoticeFixture; import leets.weeth.domain.board.domain.service.NoticeFindService; import leets.weeth.domain.file.domain.entity.File; import leets.weeth.domain.file.domain.service.FileGetService; diff --git a/src/test/java/leets/weeth/domain/board/domain/fixture/NoticeFixture.java b/src/test/java/leets/weeth/domain/board/domain/test/fixture/NoticeFixture.java similarity index 59% rename from src/test/java/leets/weeth/domain/board/domain/fixture/NoticeFixture.java rename to src/test/java/leets/weeth/domain/board/domain/test/fixture/NoticeFixture.java index 59356932..5bd9e00e 100644 --- a/src/test/java/leets/weeth/domain/board/domain/fixture/NoticeFixture.java +++ b/src/test/java/leets/weeth/domain/board/domain/test/fixture/NoticeFixture.java @@ -1,4 +1,4 @@ -package leets.weeth.domain.board.domain.fixture; +package leets.weeth.domain.board.domain.test.fixture; import leets.weeth.domain.board.domain.entity.Notice; import leets.weeth.domain.user.domain.entity.User; @@ -12,4 +12,12 @@ public static Notice createNotice(String title, User user){ .commentCount(0) .build(); } + + public static Notice createNotice(String title){ + return Notice.builder() + .title(title) + .content("내용") + .commentCount(0) + .build(); + } } From c744d9b15e3675c13e4f56bf210e2041cc9acf3d Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 18:57:52 +0900 Subject: [PATCH 076/114] =?UTF-8?q?refactor:=20=EA=B3=B5=EC=A7=80=20?= =?UTF-8?q?=EB=A0=88=ED=8F=AC=EC=A7=80=ED=84=B0=EB=A6=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95=20-=20NoticeFixture=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=EB=B0=98=EB=B3=B5?= =?UTF-8?q?=EB=AC=B8=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=AA=A9=EC=A0=81=EC=9D=B4=20=ED=8F=AC=ED=95=A8?= =?UTF-8?q?=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20-=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95(id?= =?UTF-8?q?=20=EB=82=B4=EB=A6=BC=EC=B0=A8=EC=88=9C=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=83=89=20=EA=B2=B0=EA=B3=BC=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/NoticeRepositoryTest.java | 104 +++++++----------- 1 file changed, 42 insertions(+), 62 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java index fc10f5d4..3bd1c259 100644 --- a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java @@ -2,9 +2,8 @@ import leets.weeth.config.TestContainersConfig; import leets.weeth.domain.board.domain.entity.Notice; -import leets.weeth.domain.user.domain.service.UserSaveService; +import leets.weeth.domain.board.domain.test.fixture.NoticeFixture; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; @@ -14,6 +13,9 @@ import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; +import java.util.ArrayList; +import java.util.List; + import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; @DataJpaTest @@ -25,83 +27,61 @@ class NoticeRepositoryTest { private NoticeRepository noticeRepository; @Test - void findPageBy() { + void findPageBy_공지_id_내림차순으로_조회() { // given - Notice notice1 = noticeRepository.save(Notice.builder() - .title("제목1") - .content("내용1") - .build()); - - Notice notice2 = noticeRepository.save(Notice.builder() - .title("제목2") - .content("내용2") - .build()); - - Notice notice3 = noticeRepository.save(Notice.builder() - .title("제목3") - .content("내용3") - .build()); - - Notice notice4 = noticeRepository.save(Notice.builder() - .title("제목4") - .content("내용4") - .build()); - - Notice notice5 = noticeRepository.save(Notice.builder() - .title("제목5") - .content("내용5") - .build()); + List notices = new ArrayList<>(); + for(int i = 0; i<5; i++){ + Notice notice = NoticeFixture.createNotice("공지" + i); + notices.add(notice); + } + noticeRepository.saveAll(notices); Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "id")); // when - Slice notices = noticeRepository.findPageBy(pageable); + Slice pagedNotices = noticeRepository.findPageBy(pageable); // then - assertThat(notices.getSize()).isEqualTo(3); - assertThat(notices.getContent().get(0).getTitle()).isEqualTo(notice5.getTitle()); - assertThat(notices.getContent().get(1).getTitle()).isEqualTo(notice4.getTitle()); - assertThat(notices.getContent().get(2).getTitle()).isEqualTo(notice3.getTitle()); - assertThat(notices.hasNext()).isEqualTo(true); + assertThat(pagedNotices .getSize()).isEqualTo(3); + assertThat(pagedNotices) + .extracting(Notice::getTitle) + .containsExactly( + notices.get(4).getTitle(), + notices.get(3).getTitle(), + notices.get(2).getTitle() + ); + assertThat(pagedNotices .hasNext()).isTrue(); } @Test - void search() { + void search_검색어가_포함된_공지_id_내림차순으로_조회() { // given - Notice notice1 = noticeRepository.save(Notice.builder() - .title("검색1") - .content("내용1") - .build()); + List notices = new ArrayList<>(); + for(int i = 0; i<6; i++){ + Notice notice; + if(i % 2 == 0){ + notice = NoticeFixture.createNotice("공지" + i); + } else{ + notice = NoticeFixture.createNotice("검색" + i); + } + notices.add(notice); + } - Notice notice2 = noticeRepository.save(Notice.builder() - .title("제목2") - .content("내용2") - .build()); + noticeRepository.saveAll(notices); - Notice notice3 = noticeRepository.save(Notice.builder() - .title("검색3") - .content("내용3") - .build()); - - Notice notice4 = noticeRepository.save(Notice.builder() - .title("제목4") - .content("내용4") - .build()); - - Notice notice5 = noticeRepository.save(Notice.builder() - .title("검색5") - .content("내용5") - .build()); - - Pageable pageable = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "id")); + Pageable pageable = PageRequest.of(0, 5, Sort.by(Sort.Direction.DESC, "id")); // when Slice searchedNotices = noticeRepository.search("검색", pageable); // then - assertThat(searchedNotices.getSize()).isEqualTo(2); - assertThat(searchedNotices.getContent().get(0).getTitle()).isEqualTo(notice5.getTitle()); - assertThat(searchedNotices.getContent().get(1).getTitle()).isEqualTo(notice3.getTitle()); - assertThat(searchedNotices.hasNext()).isEqualTo(true); + assertThat(searchedNotices.getContent()).hasSize(3); + assertThat(searchedNotices.getContent()) + .extracting(Notice::getTitle) + .containsExactly(notices.get(5).getTitle(), + notices.get(3).getTitle(), + notices.get(1).getTitle()); + assertThat(searchedNotices.hasNext()).isFalse(); + } } From 294faade0f6ef55bf31ce1377339495e4f6e382c Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Oct 2025 19:30:58 +0900 Subject: [PATCH 077/114] =?UTF-8?q?style:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EC=97=90=EC=84=9C=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/domain/repository/NoticeRepositoryTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java index 3bd1c259..2c017a48 100644 --- a/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/board/domain/repository/NoticeRepositoryTest.java @@ -39,10 +39,10 @@ class NoticeRepositoryTest { Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Direction.DESC, "id")); // when - Slice pagedNotices = noticeRepository.findPageBy(pageable); + Slice pagedNotices = noticeRepository.findPageBy(pageable); // then - assertThat(pagedNotices .getSize()).isEqualTo(3); + assertThat(pagedNotices.getSize()).isEqualTo(3); assertThat(pagedNotices) .extracting(Notice::getTitle) .containsExactly( @@ -50,7 +50,7 @@ class NoticeRepositoryTest { notices.get(3).getTitle(), notices.get(2).getTitle() ); - assertThat(pagedNotices .hasNext()).isTrue(); + assertThat(pagedNotices.hasNext()).isTrue(); } @Test From f3057e9e74d5a967a0b67749db05890979581d10 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 3 Nov 2025 22:07:42 +0900 Subject: [PATCH 078/114] =?UTF-8?q?test:=20NoticeSaveServiceTest=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/service/NoticeSaveServiceTest.java | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java diff --git a/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java b/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java deleted file mode 100644 index 1171c34d..00000000 --- a/src/test/java/leets/weeth/domain/board/domain/service/NoticeSaveServiceTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package leets.weeth.domain.board.domain.service; - -import leets.weeth.config.TestContainersConfig; -import leets.weeth.domain.board.domain.entity.Notice; -import leets.weeth.domain.board.domain.repository.NoticeRepository; -import leets.weeth.domain.user.domain.entity.User; -import leets.weeth.domain.user.domain.entity.enums.Department; -import leets.weeth.domain.user.domain.entity.enums.Position; -import leets.weeth.domain.user.domain.entity.enums.Role; -import leets.weeth.domain.user.domain.repository.UserRepository; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.context.annotation.Import; - -import java.util.List; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -@DataJpaTest -@Import({TestContainersConfig.class, NoticeSaveService.class}) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -class NoticeSaveServiceTest { - - @Autowired - UserRepository userRepository; - @Autowired - NoticeRepository noticeRepository; - - @Autowired - NoticeSaveService noticeSaveService; - - @Test - void save() { - // given - User user = userRepository.save(User.builder() - .email("abc@test.com") - .name("홍길동") - .position(Position.BE) - .department(Department.SW) - .role(Role.USER) - .build()); - - Notice notice = Notice.builder() - .title("제목") - .content("내용") - .user(user) - .build(); - - // when - noticeSaveService.save(notice); - - // then - List notices = noticeRepository.findAll(); - assertThat(notices).hasSize(1); - Notice savedNotice = notices.get(0); - assertThat(savedNotice.getTitle()).isEqualTo(notice.getTitle()); - assertThat(savedNotice.getContent()).isEqualTo(notice.getContent()); - assertThat(savedNotice.getUser().getId()).isEqualTo(user.getId()); - } -} From 0afb2c0ee86830a1c25ff8565ad939f39031e315 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 5 Nov 2025 16:44:09 +0900 Subject: [PATCH 079/114] =?UTF-8?q?refactor:=20Dockerfile=EC=9D=98=20base?= =?UTF-8?q?=20image=EB=A5=BC=20openjdk:17-alpine=20->=20eclipse-temurin:17?= =?UTF-8?q?-jdk-alpine=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 --- Dockerfile-dev | 4 ++-- Dockerfile-prod | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile-dev b/Dockerfile-dev index a9b6b782..e46cdb88 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,5 +1,5 @@ -# open jdk 17 버전의 alpine 리눅스 환경을 구성 -FROM openjdk:17-alpine +# eclipse-temurin 17 버전의 alpine 리눅스 환경을 구성 +FROM eclipse-temurin:17-jdk-alpine # build가 되는 시점에 JAR_FILE이라는 변수 명에 build/libs/*.jar 선언 # build/libs - gradle로 빌드했을 때 jar 파일이 생성되는 경로 diff --git a/Dockerfile-prod b/Dockerfile-prod index a20969c7..92b747a2 100644 --- a/Dockerfile-prod +++ b/Dockerfile-prod @@ -1,5 +1,5 @@ -# open jdk 17 버전의 alpine 리눅스 환경을 구성 -FROM openjdk:17-alpine +# eclipse-temurin 17 버전의 alpine 리눅스 환경을 구성 +FROM eclipse-temurin:17-jdk-alpine # build가 되는 시점에 JAR_FILE이라는 변수 명에 build/libs/*.jar 선언 # build/libs - gradle로 빌드했을 때 jar 파일이 생성되는 경로 From 58652d4a8539f7730bf8d1834b470f4b1c8ab58f Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 5 Nov 2025 17:25:28 +0900 Subject: [PATCH 080/114] =?UTF-8?q?chore:=20Spring=20Boot=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=EC=9D=84=203.5.7=EB=A1=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 205fd135..c1127aa1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.3.1' + id 'org.springframework.boot' version '3.5.7' id 'io.spring.dependency-management' version '1.1.5' } From 420a931325a54253c8dd3db2b4dfab9a3cb8c471 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 16:49:26 +0900 Subject: [PATCH 081/114] =?UTF-8?q?=20add=20:=20=20finAllByStatusOrderByCa?= =?UTF-8?q?rdinalAndName()=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/UserRepositoryTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java index f0b3642e..ae39d53f 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java @@ -11,6 +11,9 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import leets.weeth.config.TestContainersConfig; import leets.weeth.domain.user.domain.entity.Cardinal; @@ -70,4 +73,22 @@ void findAllByCardinalAndStatus() { .extracting(User::getName) .containsExactly("적순"); } + + @Test + @DisplayName("finALlByStatusOrderByCardinalAndName() : 상태별로 최신 기수순 + 이름 오름차순으로 정렬된다") + void finAllByStatusOrderByCardinalAndName() { + //given + Pageable pageable = PageRequest.of(0,10); + + //when + Slice resultSlice = userRepository.findAllByStatusOrderedByCardinalAndName(Status.ACTIVE, pageable); + List result = resultSlice.getContent(); + + //then + assertThat(result) + .hasSize(2) + .extracting(User::getName) + .containsExactly("적순2", "적순"); + } + } From f25e61d4ed63fa535729a8129c8b57947a7a8a42 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 17:04:49 +0900 Subject: [PATCH 082/114] =?UTF-8?q?=20add=20:=20=20finAllByCardinalOrderBy?= =?UTF-8?q?NameAsc()=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/repository/UserRepositoryTest.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java index ae39d53f..d2050556 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java @@ -75,7 +75,7 @@ void findAllByCardinalAndStatus() { } @Test - @DisplayName("finALlByStatusOrderByCardinalAndName() : 상태별로 최신 기수순 + 이름 오름차순으로 정렬된다") + @DisplayName("finAllByStatusOrderByCardinalAndName() : 상태별로 최신 기수순 + 이름 오름차순으로 정렬된다") void finAllByStatusOrderByCardinalAndName() { //given Pageable pageable = PageRequest.of(0,10); @@ -91,4 +91,21 @@ void finAllByStatusOrderByCardinalAndName() { .containsExactly("적순2", "적순"); } + @Test + @DisplayName("findAllByCardinalOrderByNameAsc() : Active인 유저들 중 특정 기수 + 이름 오름차순으로 정렬한다.") + void findAllByCardinalOrderByNameAsc() { + //given + Pageable pageable = PageRequest.of(0,10); + + //when + Slice resultSlice = userRepository.findAllByCardinalOrderByNameAsc(Status.ACTIVE, cardinal7,pageable); + List result = resultSlice.getContent(); + + //then + assertThat(result) + .hasSize(1) + .extracting(User::getName) + .containsExactly("적순"); + } + } From 59627cb6311f2be28f5e92e91e7cb0a182da0d9f Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 17:31:42 +0900 Subject: [PATCH 083/114] =?UTF-8?q?feat=20:=20Service=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20CardinalGetService?= =?UTF-8?q?=5FfindByAdminSide()=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CardinalGetServiceTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java diff --git a/src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java new file mode 100644 index 00000000..879085d6 --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java @@ -0,0 +1,45 @@ +package leets.weeth.domain.user.domain.service; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.mockito.BDDMockito.*; + +import java.util.Optional; + +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 leets.weeth.domain.user.domain.entity.Cardinal; +import leets.weeth.domain.user.domain.repository.CardinalRepository; + +@ExtendWith(MockitoExtension.class) +public class CardinalGetServiceTest { + + // validateCardinal() 검증로직 확인 + @Mock + private CardinalRepository cardinalRepository; + + @InjectMocks + private CardinalGetService cardinalGetService; + + @Test + @DisplayName("findByAdminSide() : 존재하지 않는 기수를 넣었을 때 새로 저장되는지 확인") + void findByAdminSide() { + //given + given(cardinalRepository.findByCardinalNumber(7)) + .willReturn(Optional.empty()); + + given(cardinalRepository.save(any(Cardinal.class))). + willReturn(Cardinal.builder().cardinalNumber(7).build()); + + //when + Cardinal result = cardinalGetService.findByAdminSide(7); + + //then + assertThat(result.getCardinalNumber()).isEqualTo(7); + } + +} From f07dc1c4873e4a6d37f88f705e0e1ee8504b4150 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 17:40:31 +0900 Subject: [PATCH 084/114] =?UTF-8?q?=20add=20:=20CardinalGetService=5Fvalid?= =?UTF-8?q?ateCardinal()=20=EC=A4=91=EB=B3=B5=EA=B8=B0=EC=88=98=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/CardinalGetServiceTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java index 879085d6..b564019d 100644 --- a/src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/service/CardinalGetServiceTest.java @@ -12,6 +12,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import leets.weeth.domain.user.application.exception.DuplicateCardinalException; import leets.weeth.domain.user.domain.entity.Cardinal; import leets.weeth.domain.user.domain.repository.CardinalRepository; @@ -42,4 +43,28 @@ void findByAdminSide() { assertThat(result.getCardinalNumber()).isEqualTo(7); } + @Test + @DisplayName("validateCardinal() : 중복된 기수 저장을 방지하고 예외를 던지는지 확인") + void validateCardinal() { + //given + given(cardinalRepository.findByCardinalNumber(7)) + .willReturn(Optional.of(Cardinal.builder().cardinalNumber(7).build())); + + //when&then + assertThatThrownBy(() -> cardinalGetService.validateCardinal(7)) + .isInstanceOf(DuplicateCardinalException.class); + } + + @Test + @DisplayName("validateCardinal() : 중복되지 않는 기수라면 예외를 던지지않고 잘 저장하는지 확인") + void validateCardinal_noException() { + //given + given(cardinalRepository.findByCardinalNumber(7)) + .willReturn(Optional.empty()); + + //when&then + assertThatCode(() -> cardinalGetService.validateCardinal(7)) + .doesNotThrowAnyException(); + } + } From 0142a51d2b55272ba163d88c70efcc57a7c4cf8c Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 18:20:13 +0900 Subject: [PATCH 085/114] =?UTF-8?q?feat=20:=20UserCardinalGetServiceTest?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20notCon?= =?UTF-8?q?tains()=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/UserCardinalGetServiceTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java diff --git a/src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java new file mode 100644 index 00000000..308acbfb --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java @@ -0,0 +1,48 @@ +package leets.weeth.domain.user.domain.service; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.mockito.BDDMockito.*; + +import java.util.List; + +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 leets.weeth.domain.user.domain.entity.UserCardinal; +import leets.weeth.domain.user.domain.repository.UserCardinalRepository; +import leets.weeth.domain.user.test.fixture.CardinalTestFixture; +import leets.weeth.domain.user.test.fixture.UserTestFixture; + +@ExtendWith(MockitoExtension.class) +public class UserCardinalGetServiceTest { + + @Mock + private UserCardinalRepository userCardinalRepository; + + @InjectMocks + private UserCardinalGetService userCardinalGetService; + + + @Test + @DisplayName("notContains() : 유저의 기수 목록 중, 특정 기수가 없는지 확인 ") + void notContains() { + //given + var user = UserTestFixture.createActiveUser1(); + var existingCardinal = CardinalTestFixture.createCardinal(7,2025,2); + var targetCardinal = CardinalTestFixture.createCardinal(8,2026,1); + var userCardinal = new UserCardinal(user, existingCardinal); + + given(userCardinalRepository.findAllByUserOrderByCardinalCardinalNumberDesc(user)) + .willReturn(List.of(userCardinal)); + //when + boolean result = userCardinalGetService.notContains(user, targetCardinal); + + //then + assertThat(result).isTrue(); + } + +} From 6ba1850d7795ee24c0d89238baecdeb9dade731d Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 18:32:13 +0900 Subject: [PATCH 086/114] =?UTF-8?q?add=20:=20isCurrent=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20case=EB=B3=84=EB=A1=9C=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/UserCardinalGetServiceTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java index 308acbfb..c6e30381 100644 --- a/src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/service/UserCardinalGetServiceTest.java @@ -12,6 +12,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import leets.weeth.domain.user.application.exception.CardinalNotFoundException; import leets.weeth.domain.user.domain.entity.UserCardinal; import leets.weeth.domain.user.domain.repository.UserCardinalRepository; import leets.weeth.domain.user.test.fixture.CardinalTestFixture; @@ -38,6 +39,7 @@ void notContains() { given(userCardinalRepository.findAllByUserOrderByCardinalCardinalNumberDesc(user)) .willReturn(List.of(userCardinal)); + //when boolean result = userCardinalGetService.notContains(user, targetCardinal); @@ -45,4 +47,57 @@ void notContains() { assertThat(result).isTrue(); } + @Test + @DisplayName("isCurrent() : 현재 유저의 최신 기수보다 최신 기수인지 확인") + void isCurrent() { + //given + var user = UserTestFixture.createActiveUser1(); + var oldCardinal = CardinalTestFixture.createCardinal(7,2025,2); + var newCardinal = CardinalTestFixture.createCardinal(8,2026,1); + var userCardinal = new UserCardinal(user, oldCardinal); + + given(userCardinalRepository.findAllByUserOrderByCardinalCardinalNumberDesc(user)) + .willReturn(List.of(userCardinal)); + + //when + boolean result = userCardinalGetService.isCurrent(user, newCardinal); + + //then + assertThat(result).isTrue(); + } + + @Test + @DisplayName("isCurrent(): 새 기수가 기존 최대보다 작으면 false 반환") + void isCurrent_returnsFalse_whenOlderCardinal() { + // given + var user = UserTestFixture.createActiveUser1(); + var oldCardinal = CardinalTestFixture.createCardinal(7, 2025, 1); + var newCardinal = CardinalTestFixture.createCardinal(6, 2024, 2); + var userCardinal = new UserCardinal(user, oldCardinal); + + given(userCardinalRepository.findAllByUserOrderByCardinalCardinalNumberDesc(user)) + .willReturn(List.of(userCardinal)); + + // when + boolean result = userCardinalGetService.isCurrent(user, newCardinal); + + // then + assertThat(result).isFalse(); + } + + @Test + @DisplayName("isCurrent(): 유저가 어떤 기수도 가지고 있지 않으면 CardinalNotFoundException 발생") + void isCurrent_throwsException_whenUserHasNoCardinal() { + // given + var user = UserTestFixture.createActiveUser1(); + var newCardinal = CardinalTestFixture.createCardinal(8, 2026, 1); + + given(userCardinalRepository.findAllByUserOrderByCardinalCardinalNumberDesc(user)) + .willReturn(List.of()); + + // when & then + assertThatThrownBy(() -> userCardinalGetService.isCurrent(user, newCardinal)) + .isInstanceOf(CardinalNotFoundException.class); + } + } From a821efb859d830ec28e06501c93e70ae9fca048f Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 19:07:02 +0900 Subject: [PATCH 087/114] =?UTF-8?q?=20feat=20:=20UserGetServiceTest=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20find=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/service/UserGetServiceTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java diff --git a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java new file mode 100644 index 00000000..724848ae --- /dev/null +++ b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java @@ -0,0 +1,38 @@ +package leets.weeth.domain.user.domain.service; + +import leets.weeth.domain.user.application.exception.UserNotFoundException; +import leets.weeth.domain.user.domain.repository.UserRepository; +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.annotation.DirtiesContext; + +import java.util.Optional; +import static org.mockito.BDDMockito.given; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +class UserGetServiceTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserGetService userGetService; + + @Test + @DisplayName("find(Long Id) : 존재하지 않는 유저일 때 예외를 던진다") + void find_userNotFound_throwsException() { + //given + Long userId = Long.valueOf(1L); + given(userRepository.findById(userId)).willReturn(Optional.empty()); + + // when & then + assertThrows(UserNotFoundException.class, () -> userGetService.find(userId)); + } + +} From 9dddc28dae522ceca510094d944bb1e1116cce93 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 19:10:00 +0900 Subject: [PATCH 088/114] =?UTF-8?q?add=20:=20find=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=EB=A1=9C=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/domain/service/UserGetServiceTest.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java index 724848ae..2314b955 100644 --- a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java @@ -26,7 +26,7 @@ class UserGetServiceTest { @Test @DisplayName("find(Long Id) : 존재하지 않는 유저일 때 예외를 던진다") - void find_userNotFound_throwsException() { + void find_id_userNotFound_throwsException() { //given Long userId = Long.valueOf(1L); given(userRepository.findById(userId)).willReturn(Optional.empty()); @@ -35,4 +35,15 @@ void find_userNotFound_throwsException() { assertThrows(UserNotFoundException.class, () -> userGetService.find(userId)); } + @Test + @DisplayName("find(String email) : 존재하지 않는 유저일 때 예외를 던진다") + void find_email_userNotFound_throwsException() { + //given + String email = "test@test.com"; + given(userRepository.findByEmail(email)).willReturn(Optional.empty()); + + //when & then + assertThrows(UserNotFoundException.class, () -> userGetService.find(email)); + } + } From 27acd569ae877bfb21085d43dc252313e87b175a Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sun, 2 Nov 2025 19:19:46 +0900 Subject: [PATCH 089/114] =?UTF-8?q?=20add=20:=20findAll(Pageable=20pageabl?= =?UTF-8?q?e)=20=EB=B9=88=20=EC=8A=AC=EB=9D=BC=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20=EC=8B=9C,=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/service/UserGetServiceTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java index 2314b955..6c8f2499 100644 --- a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java @@ -1,6 +1,7 @@ package leets.weeth.domain.user.domain.service; import leets.weeth.domain.user.application.exception.UserNotFoundException; +import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.repository.UserRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -8,9 +9,16 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.data.domain.SliceImpl; +import java.util.List; import java.util.Optional; + +import static org.mockito.ArgumentMatchers.*; import static org.mockito.BDDMockito.given; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -46,4 +54,20 @@ void find_email_userNotFound_throwsException() { assertThrows(UserNotFoundException.class, () -> userGetService.find(email)); } + @Test + @DisplayName("findAll(Pageable pageable) : 빈 슬라이스 반환 시 , 유저 예외 던진다") + void findAll_userNotFound_throwsException() { + //given + Pageable pageable = PageRequest.of(0, 10); + Slice emptySlice = new SliceImpl<>(List.of(), pageable, false); + + given(userRepository.findAllByStatusOrderedByCardinalAndName(any(), eq(pageable))) + .willReturn(emptySlice); + + //when & then + assertThrows(UserNotFoundException.class, + () -> userGetService.findAll(pageable)); + + } + } From 04656f4710de60abc41d38837ef43a8b854117a0 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 4 Nov 2025 23:16:46 +0900 Subject: [PATCH 090/114] =?UTF-8?q?=20fix=20:=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/domain/user/domain/repository/UserRepositoryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java index d2050556..8421d098 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java @@ -76,7 +76,7 @@ void findAllByCardinalAndStatus() { @Test @DisplayName("finAllByStatusOrderByCardinalAndName() : 상태별로 최신 기수순 + 이름 오름차순으로 정렬된다") - void finAllByStatusOrderByCardinalAndName() { + void findAllByStatusOrderByCardinalAndName() { //given Pageable pageable = PageRequest.of(0,10); From b203972c90f09a4269825c037ae0ff8137b3660b Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 4 Nov 2025 23:17:38 +0900 Subject: [PATCH 091/114] =?UTF-8?q?=20fix=20:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/domain/user/domain/service/UserGetServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java index 6c8f2499..a99b3072 100644 --- a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; @ExtendWith(MockitoExtension.class) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) class UserGetServiceTest { @Mock From 71d10c6a46a4e6f3583669c81cb99b8a42ea44ca Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Tue, 4 Nov 2025 23:23:01 +0900 Subject: [PATCH 092/114] =?UTF-8?q?=20fix=20:=20=EC=98=A4=ED=86=A0?= =?UTF-8?q?=EB=B0=95=EC=8B=B1=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=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 --- .../weeth/domain/user/domain/service/UserGetServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java index a99b3072..36877c2b 100644 --- a/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/service/UserGetServiceTest.java @@ -35,7 +35,7 @@ class UserGetServiceTest { @DisplayName("find(Long Id) : 존재하지 않는 유저일 때 예외를 던진다") void find_id_userNotFound_throwsException() { //given - Long userId = Long.valueOf(1L); + Long userId = 1L; given(userRepository.findById(userId)).willReturn(Optional.empty()); // when & then From 5ed0f0453f3f034ceddca321c4ab4895bef7e822 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Wed, 5 Nov 2025 18:57:53 +0900 Subject: [PATCH 093/114] =?UTF-8?q?=20fix:=20DisplayName=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=EB=AA=85=20=EC=98=A4=ED=83=80=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 --- .../weeth/domain/user/domain/repository/UserRepositoryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java index 8421d098..110efc5f 100644 --- a/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java +++ b/src/test/java/leets/weeth/domain/user/domain/repository/UserRepositoryTest.java @@ -75,7 +75,7 @@ void findAllByCardinalAndStatus() { } @Test - @DisplayName("finAllByStatusOrderByCardinalAndName() : 상태별로 최신 기수순 + 이름 오름차순으로 정렬된다") + @DisplayName("findAllByStatusOrderByCardinalAndName() : 상태별로 최신 기수순 + 이름 오름차순으로 정렬된다") void findAllByStatusOrderByCardinalAndName() { //given Pageable pageable = PageRequest.of(0,10); From 46b10868eb4a9fd3da82f391312d5cfe13fa3f72 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Wed, 5 Nov 2025 19:14:18 +0900 Subject: [PATCH 094/114] =?UTF-8?q?feat:=20=EC=8B=9C=EC=9E=91=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=2010=EB=B6=84=20=EC=A0=84=EB=B6=80=ED=84=B0=20?= =?UTF-8?q?=EC=B6=9C=EC=84=9D=EC=9D=B4=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../usecase/AttendanceUseCaseImpl.java | 2 +- .../usecase/AttendanceUseCaseImplTest.java | 81 +++++++++++++++---- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/main/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java b/src/main/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java index 955172a1..9840ceac 100644 --- a/src/main/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImpl.java @@ -44,7 +44,7 @@ public void checkIn(Long userId, Integer code) throws AttendanceCodeMismatchExce LocalDateTime now = LocalDateTime.now(); Attendance todayMeeting = user.getAttendances().stream() - .filter(attendance -> attendance.getMeeting().getStart().isBefore(now) + .filter(attendance -> attendance.getMeeting().getStart().minusMinutes(10).isBefore(now) && attendance.getMeeting().getEnd().isAfter(now)) .findAny() .orElseThrow(AttendanceNotFoundException::new); diff --git a/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java index c88766fb..5baa383b 100644 --- a/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java +++ b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java @@ -1,20 +1,5 @@ package leets.weeth.domain.attendance.application.usecase; -import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.BDDMockito.*; - -import java.time.LocalDate; -import java.util.List; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -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 leets.weeth.domain.attendance.application.dto.AttendanceDTO; import leets.weeth.domain.attendance.application.exception.AttendanceCodeMismatchException; import leets.weeth.domain.attendance.application.exception.AttendanceNotFoundException; @@ -30,6 +15,23 @@ import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.service.UserCardinalGetService; import leets.weeth.domain.user.domain.service.UserGetService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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 java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +import static leets.weeth.domain.attendance.test.fixture.AttendanceTestFixture.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.BDDMockito.*; @ExtendWith(MockitoExtension.class) public class AttendanceUseCaseImplTest { @@ -76,6 +78,55 @@ void find_todayMeeting_filtersDispersedAttendances() { then(attendanceMapper).shouldHaveNoMoreInteractions(); } + @Test + @DisplayName("10분 전부터 출석이 가능한지 확인") + void checkIn_10MinutesBeforeMeeting_ShouldSucceed() { + // given + LocalDateTime now = LocalDateTime.now(); + Meeting meeting = Meeting.builder() + .start(now.plusMinutes(5)) // 5분 뒤 시작 (checkIn 로직의 '10분 전' 범위 내) + .end(now.plusHours(2)) + .code(1234) + .title("Today") + .cardinal(1) + .build(); + + User user = createActiveUserWithAttendances( + "이지훈", List.of(meeting) + ); + + when(userGetService.find(userId)).thenReturn(user); + + + // when & then + assertDoesNotThrow(() -> attendanceUseCase.checkIn(userId, 1234)); + verify(attendanceUpdateService, times(1)).attend(any(Attendance.class)); + } + + @Test + @DisplayName("11분 전에 출석시 오류 확인") + void checkIn_10MinutesBeforeMeeting_ShouldFailed() { + // given + LocalDateTime now = LocalDateTime.now(); + Meeting meeting = Meeting.builder() + .start(now.plusMinutes(11)) // 5분 뒤 시작 (checkIn 로직의 '10분 전' 범위 내) + .end(now.plusHours(2)) + .code(1234) + .title("Today") + .cardinal(1) + .build(); + + User user = createActiveUserWithAttendances( + "이지훈", List.of(meeting) + ); + + when(userGetService.find(userId)).thenReturn(user); + + + // when & then + assertThatThrownBy(() -> attendanceUseCase.checkIn(userId, 1234)) + .isInstanceOf(AttendanceNotFoundException.class); + } @Test @DisplayName("find: '시작/종료 날짜가 모두 오늘'인 출석이 없다면, mapper.toMainDto(user, null)을 호출") void find_noExactToday_returnsNullMapped() { From d633259adfd24859c6c8f863109d5a9acdfa7cc6 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Wed, 5 Nov 2025 19:22:04 +0900 Subject: [PATCH 095/114] =?UTF-8?q?docs:=20=EC=A3=BC=EC=84=9D=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 --- .../application/usecase/AttendanceUseCaseImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java index 5baa383b..eb3957ca 100644 --- a/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java +++ b/src/test/java/leets/weeth/domain/attendance/application/usecase/AttendanceUseCaseImplTest.java @@ -109,7 +109,7 @@ void checkIn_10MinutesBeforeMeeting_ShouldFailed() { // given LocalDateTime now = LocalDateTime.now(); Meeting meeting = Meeting.builder() - .start(now.plusMinutes(11)) // 5분 뒤 시작 (checkIn 로직의 '10분 전' 범위 내) + .start(now.plusMinutes(11)) // 11분뒤 시작 -> 오류 발생해야함 .end(now.plusHours(2)) .code(1234) .title("Today") From 2da7a127a362625507e686c7adec7efdfad1db94 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Wed, 5 Nov 2025 22:44:50 +0900 Subject: [PATCH 096/114] =?UTF-8?q?HOTFIX:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0?= =?UTF-8?q?=20=EB=B2=84=EC=A0=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c1127aa1..33b69c6b 100644 --- a/build.gradle +++ b/build.gradle @@ -60,7 +60,7 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' // Swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14' // AWS Spring Cloud implementation 'software.amazon.awssdk:s3:2.19.1' From 0fc7426058930012e17622ed74702e5e77a83042 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Thu, 6 Nov 2025 21:49:40 +0900 Subject: [PATCH 097/114] =?UTF-8?q?=20fix=20:=20em.flush()=20=EC=99=80=20c?= =?UTF-8?q?lear()=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=A9=94=EB=AA=A8=EB=A6=AC=EC=99=80=20DB=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/application/usecase/MeetingUseCaseImpl.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java b/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java index ab80aea3..edfdd02e 100644 --- a/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java @@ -1,5 +1,7 @@ package leets.weeth.domain.schedule.application.usecase; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import leets.weeth.domain.attendance.domain.entity.Attendance; import leets.weeth.domain.attendance.domain.service.AttendanceDeleteService; import leets.weeth.domain.attendance.domain.service.AttendanceGetService; @@ -44,6 +46,9 @@ public class MeetingUseCaseImpl implements MeetingUseCase { private final AttendanceUpdateService attendanceUpdateService; private final CardinalGetService cardinalGetService; + @PersistenceContext + private final EntityManager em; + @Override public Response find(Long userId, Long meetingId) { User user = userGetService.find(userId); @@ -103,6 +108,10 @@ public void delete(Long meetingId) { attendanceUpdateService.updateUserAttendanceByStatus(attendances); + em.flush(); + em.clear(); + + attendanceDeleteService.deleteAll(meeting); meetingDeleteService.delete(meeting); } From a630ef26ce3ccb470364088d75af9a5de8137b10 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Thu, 6 Nov 2025 22:13:08 +0900 Subject: [PATCH 098/114] =?UTF-8?q?del:=20final=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/schedule/application/usecase/MeetingUseCaseImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java b/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java index edfdd02e..62510a00 100644 --- a/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java @@ -47,7 +47,7 @@ public class MeetingUseCaseImpl implements MeetingUseCase { private final CardinalGetService cardinalGetService; @PersistenceContext - private final EntityManager em; + private EntityManager em; @Override public Response find(Long userId, Long meetingId) { From e0977733389ea613ff0534b03af60f1776ea3e91 Mon Sep 17 00:00:00 2001 From: seokjun01 Date: Sat, 8 Nov 2025 02:06:54 +0900 Subject: [PATCH 099/114] =?UTF-8?q?=20fix=20:=20=EA=B0=9C=ED=96=89=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 --- .../domain/schedule/application/usecase/MeetingUseCaseImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java b/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java index 62510a00..8889a49b 100644 --- a/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/schedule/application/usecase/MeetingUseCaseImpl.java @@ -111,7 +111,6 @@ public void delete(Long meetingId) { em.flush(); em.clear(); - attendanceDeleteService.deleteAll(meeting); meetingDeleteService.delete(meeting); } From 3100ed09a2b1bf3a89418080c5e1c40ab7860ad0 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 18:43:54 +0900 Subject: [PATCH 100/114] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20DTO=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/global/auth/apple/dto/ApplePublicKey.java | 13 +++++++++++++ .../global/auth/apple/dto/ApplePublicKeys.java | 8 ++++++++ .../global/auth/apple/dto/AppleTokenResponse.java | 10 ++++++++++ .../weeth/global/auth/apple/dto/AppleUserInfo.java | 11 +++++++++++ 4 files changed, 42 insertions(+) create mode 100644 src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKey.java create mode 100644 src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKeys.java create mode 100644 src/main/java/leets/weeth/global/auth/apple/dto/AppleTokenResponse.java create mode 100644 src/main/java/leets/weeth/global/auth/apple/dto/AppleUserInfo.java diff --git a/src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKey.java b/src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKey.java new file mode 100644 index 00000000..54d68729 --- /dev/null +++ b/src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKey.java @@ -0,0 +1,13 @@ +package leets.weeth.global.auth.apple.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ApplePublicKey( + String kty, + String kid, + String use, + String alg, + String n, + String e +) { +} diff --git a/src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKeys.java b/src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKeys.java new file mode 100644 index 00000000..909d2fc5 --- /dev/null +++ b/src/main/java/leets/weeth/global/auth/apple/dto/ApplePublicKeys.java @@ -0,0 +1,8 @@ +package leets.weeth.global.auth.apple.dto; + +import java.util.List; + +public record ApplePublicKeys( + List keys +) { +} diff --git a/src/main/java/leets/weeth/global/auth/apple/dto/AppleTokenResponse.java b/src/main/java/leets/weeth/global/auth/apple/dto/AppleTokenResponse.java new file mode 100644 index 00000000..2c52a44a --- /dev/null +++ b/src/main/java/leets/weeth/global/auth/apple/dto/AppleTokenResponse.java @@ -0,0 +1,10 @@ +package leets.weeth.global.auth.apple.dto; + +public record AppleTokenResponse( + String access_token, + String token_type, + Long expires_in, + String refresh_token, + String id_token +) { +} diff --git a/src/main/java/leets/weeth/global/auth/apple/dto/AppleUserInfo.java b/src/main/java/leets/weeth/global/auth/apple/dto/AppleUserInfo.java new file mode 100644 index 00000000..8bec9569 --- /dev/null +++ b/src/main/java/leets/weeth/global/auth/apple/dto/AppleUserInfo.java @@ -0,0 +1,11 @@ +package leets.weeth.global.auth.apple.dto; + +import lombok.Builder; + +@Builder +public record AppleUserInfo( + String appleId, + String email, + Boolean emailVerified +) { +} From 99e1ac3390026a4eaba44a22ff4912238f069e48 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 18:44:29 +0900 Subject: [PATCH 101/114] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82381f45..7b6908f1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,6 +27,14 @@ auth: grant_type: ${KAKAO_GRANT_TYPE} token_uri: ${KAKAO_TOKEN_URI} user_info_uri: ${KAKAO_USER_INFO_URI} + apple: + client_id: ${APPLE_CLIENT_ID} + team_id: ${APPLE_TEAM_ID} + key_id: ${APPLE_KEY_ID} + redirect_uri: ${APPLE_REDIRECT_URI} + token_uri: https://appleid.apple.com/auth/token + keys_uri: https://appleid.apple.com/auth/keys + private_key_path: AuthKey_5JL5Z7K6J7.p8 jwt: private-key: ${JWT_PRIVATE_KEY} public-key: ${JWT_PUBLIC_KEY} From 5d4d7f4b5256b7b4acf0d038f4c539b6f8a7e5c4 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 18:45:48 +0900 Subject: [PATCH 102/114] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/apple/AppleAuthService.java | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java diff --git a/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java b/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java new file mode 100644 index 00000000..bdf690fd --- /dev/null +++ b/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java @@ -0,0 +1,231 @@ +package leets.weeth.global.auth.apple; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import leets.weeth.global.auth.apple.dto.ApplePublicKey; +import leets.weeth.global.auth.apple.dto.ApplePublicKeys; +import leets.weeth.global.auth.apple.dto.AppleTokenResponse; +import leets.weeth.global.auth.apple.dto.AppleUserInfo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; + +import java.math.BigInteger; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.RSAPublicKeySpec; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.Base64; +import java.util.Date; +import java.util.Map; + +@Service +@Slf4j +public class AppleAuthService { + + @Value("${auth.providers.apple.client_id}") + private String appleClientId; + + @Value("${auth.providers.apple.team_id}") + private String appleTeamId; + + @Value("${auth.providers.apple.key_id}") + private String appleKeyId; + + @Value("${auth.providers.apple.redirect_uri}") + private String redirectUri; + + @Value("${auth.providers.apple.token_uri}") + private String tokenUri; + + @Value("${auth.providers.apple.keys_uri}") + private String keysUri; + + @Value("${auth.providers.apple.private_key_path}") + private String privateKeyPath; + + private final RestClient restClient = RestClient.create(); + + /** + * Authorization code로 애플 토큰 요청 + * client_secret은 JWT로 생성 (ES256 알고리즘) + */ + public AppleTokenResponse getAppleToken(String authCode) { + String clientSecret = generateClientSecret(); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("grant_type", "authorization_code"); + body.add("client_id", appleClientId); + body.add("client_secret", clientSecret); + body.add("code", authCode); + body.add("redirect_uri", redirectUri); + + return restClient.post() + .uri(tokenUri) + .body(body) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .retrieve() + .body(AppleTokenResponse.class); + } + + /** + * ID Token 검증 및 사용자 정보 추출 + * 애플은 별도 userInfo 엔드포인트가 없고 ID Token에 정보가 포함됨 + */ + public AppleUserInfo verifyAndDecodeIdToken(String idToken) { + try { + // 1. ID Token의 헤더에서 kid 추출 + String[] tokenParts = idToken.split("\\."); + String header = new String(Base64.getUrlDecoder().decode(tokenParts[0])); + Map headerMap = parseJson(header); + String kid = (String) headerMap.get("kid"); + + // 2. 애플 공개키 가져오기 + ApplePublicKeys publicKeys = restClient.get() + .uri(keysUri) + .retrieve() + .body(ApplePublicKeys.class); + + // 3. kid와 일치하는 공개키 찾기 + ApplePublicKey matchedKey = publicKeys.keys().stream() + .filter(key -> key.kid().equals(kid)) + .findFirst() + .orElseThrow(() -> new RuntimeException("일치하는 애플 공개키를 찾을 수 없습니다.")); + + // 4. 공개키로 ID Token 검증 + PublicKey publicKey = generatePublicKey(matchedKey); + Claims claims = Jwts.parserBuilder() + .setSigningKey(publicKey) + .build() + .parseClaimsJws(idToken) + .getBody(); + + // 5. Claims 검증 + validateClaims(claims); + + // 6. 사용자 정보 추출 + String appleId = claims.getSubject(); + String email = claims.get("email", String.class); + Boolean emailVerified = claims.get("email_verified", Boolean.class); + + return AppleUserInfo.builder() + .appleId(appleId) + .email(email) + .emailVerified(emailVerified != null ? emailVerified : false) + .build(); + + } catch (Exception e) { + log.error("애플 ID Token 검증 실패", e); + throw new RuntimeException("애플 ID Token 검증 실패: " + e.getMessage()); + } + } + + /** + * 애플 로그인용 client_secret 생성 + * ES256 알고리즘으로 JWT 생성 (p8 키 파일 사용) + */ + private String generateClientSecret() { + try { + // p8 파일에서 Private Key 읽기 + ClassPathResource resource = new ClassPathResource(privateKeyPath); + String privateKeyContent = new String(Files.readAllBytes(resource.getFile().toPath())); + + // PEM 형식의 헤더/푸터 제거 + privateKeyContent = privateKeyContent + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + + // Private Key 객체 생성 + byte[] keyBytes = Base64.getDecoder().decode(privateKeyContent); + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + PrivateKey privateKey = keyFactory.generatePrivate( + new java.security.spec.PKCS8EncodedKeySpec(keyBytes) + ); + + // JWT 생성 + LocalDateTime now = LocalDateTime.now(); + Date issuedAt = Date.from(now.atZone(ZoneId.systemDefault()).toInstant()); + Date expiration = Date.from(now.plusMonths(5).atZone(ZoneId.systemDefault()).toInstant()); + + return Jwts.builder() + .setHeaderParam("kid", appleKeyId) + .setHeaderParam("alg", "ES256") + .setIssuer(appleTeamId) + .setIssuedAt(issuedAt) + .setExpiration(expiration) + .setAudience("https://appleid.apple.com") + .setSubject(appleClientId) + .signWith(privateKey, SignatureAlgorithm.ES256) + .compact(); + + } catch (Exception e) { + log.error("애플 Client Secret 생성 실패", e); + throw new RuntimeException("애플 Client Secret 생성 실패: " + e.getMessage()); + } + } + + /** + * 애플 공개키로부터 PublicKey 객체 생성 + */ + private PublicKey generatePublicKey(ApplePublicKey applePublicKey) { + try { + byte[] nBytes = Base64.getUrlDecoder().decode(applePublicKey.n()); + byte[] eBytes = Base64.getUrlDecoder().decode(applePublicKey.e()); + + BigInteger n = new BigInteger(1, nBytes); + BigInteger e = new BigInteger(1, eBytes); + + RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + + return keyFactory.generatePublic(publicKeySpec); + } catch (Exception ex) { + log.error("애플 공개키 생성 실패", ex); + throw new RuntimeException("애플 공개키 생성 실패: " + ex.getMessage()); + } + } + + /** + * ID Token의 Claims 검증 + */ + private void validateClaims(Claims claims) { + String iss = claims.getIssuer(); + String aud = claims.getAudience(); + + if (!iss.equals("https://appleid.apple.com")) { + throw new RuntimeException("유효하지 않은 발급자(issuer)입니다."); + } + + if (!aud.equals(appleClientId)) { + throw new RuntimeException("유효하지 않은 수신자(audience)입니다."); + } + + Date expiration = claims.getExpiration(); + if (expiration.before(new Date())) { + throw new RuntimeException("만료된 ID Token입니다."); + } + } + + /** + * JSON 문자열을 Map으로 파싱 + */ + @SuppressWarnings("unchecked") + private Map parseJson(String json) { + try { + com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); + return objectMapper.readValue(json, Map.class); + } catch (Exception e) { + throw new RuntimeException("JSON 파싱 실패: " + e.getMessage()); + } + } +} From f92a38446d8f4744b2bfbdd077a1e0f8db962bec Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 18:46:37 +0900 Subject: [PATCH 103/114] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=EC=9D=84=20=EC=9C=84=ED=95=9C=20custom=20gra?= =?UTF-8?q?nt=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OAuth2AuthorizationServerConfig.java | 84 ++++++++++++++----- .../grant/AppleAuthenticationProvider.java | 73 ++++++++++++++++ .../sas/config/grant/AppleGrantType.java | 10 +++ ...eIdentityTokenAuthenticationConverter.java | 37 ++++++++ ...AppleIdentityTokenAuthenticationToken.java | 22 +++++ 5 files changed, 207 insertions(+), 19 deletions(-) create mode 100644 src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java create mode 100644 src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java create mode 100644 src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java create mode 100644 src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java diff --git a/src/main/java/leets/weeth/global/sas/config/OAuth2AuthorizationServerConfig.java b/src/main/java/leets/weeth/global/sas/config/OAuth2AuthorizationServerConfig.java index 86183202..a1d39fa6 100644 --- a/src/main/java/leets/weeth/global/sas/config/OAuth2AuthorizationServerConfig.java +++ b/src/main/java/leets/weeth/global/sas/config/OAuth2AuthorizationServerConfig.java @@ -5,13 +5,12 @@ import com.nimbusds.jose.proc.SecurityContext; import leets.weeth.domain.user.domain.entity.SecurityUser; import leets.weeth.domain.user.domain.service.UserGetService; +import leets.weeth.global.auth.apple.AppleAuthService; import leets.weeth.global.auth.kakao.KakaoAuthService; import leets.weeth.global.sas.application.exception.Oauth2JwtTokenException; import leets.weeth.global.sas.application.property.OauthProperties; import leets.weeth.global.sas.config.authentication.ProviderAwareEntryPoint; -import leets.weeth.global.sas.config.grant.KakaoAccessTokenAuthenticationConverter; -import leets.weeth.global.sas.config.grant.KakaoAuthenticationProvider; -import leets.weeth.global.sas.config.grant.KakaoGrantType; +import leets.weeth.global.sas.config.grant.*; import leets.weeth.global.sas.domain.repository.OAuth2AuthorizationGrantAuthorizationRepository; import leets.weeth.global.sas.domain.service.RedisOAuth2AuthorizationService; import lombok.RequiredArgsConstructor; @@ -31,7 +30,6 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; @@ -56,6 +54,7 @@ public class OAuth2AuthorizationServerConfig { private final ProviderAwareEntryPoint entryPoint; private final KakaoAuthService kakaoAuthService; + private final AppleAuthService appleAuthService; private final UserGetService userGetService; private final OauthProperties props; @@ -68,25 +67,57 @@ public class OAuth2AuthorizationServerConfig { @Order(1) // 우선순위를 기본 filter보다 높게 설정 public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, KakaoAccessTokenAuthenticationConverter kakaoConverter, - KakaoAuthenticationProvider kakaoProvider) throws Exception { - OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); + KakaoAuthenticationProvider kakaoProvider, + AppleIdentityTokenAuthenticationConverter appleConverter, + AppleAuthenticationProvider appleProvider) throws Exception { // entryPoint 주입 - http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) - .oidc(Customizer.withDefaults()) - .tokenEndpoint(token -> token - .accessTokenRequestConverters(c -> c.add(kakaoConverter)) - .authenticationProviders(p -> p.add(kakaoProvider)) - ); + // 1. Configurer 인스턴스 생성 (공식 템플릿 방식) + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + OAuth2AuthorizationServerConfigurer.authorizationServer(); - http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); + http + // 2. 이 필터체인이 적용될 엔드포인트를 명시적으로 지정 (템플릿 방식) + .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) - // 커스텀 EntryPoint (provider 파라미터 해석 → 302 /oauth2/authorization/{provider}) - http.exceptionHandling(e -> e.defaultAuthenticationEntryPointFor( - entryPoint, rq -> rq.getRequestURI().startsWith("/oauth2/authorize"))); + // 3. .with()를 사용하여 Configurer 적용 및 커스텀 (템플릿 방식) + .with(authorizationServerConfigurer, (authorizationServer) -> + authorizationServer + .oidc(Customizer.withDefaults()) // OIDC 활성화 - return http - .csrf(csrf -> csrf.ignoringRequestMatchers("/oauth2/**", "/.well-known/**")) - .build(); + // 4. [사용자 정의] 토큰 엔드포인트 커스텀 로직 삽입 + .tokenEndpoint(token -> token + .accessTokenRequestConverters(c -> { + c.add(kakaoConverter); + c.add(appleConverter); + }) + .authenticationProviders(p -> { + p.add(kakaoProvider); + p.add(appleProvider); + }) + ) + ) + + // 5. 엔드포인트에 대한 기본 인증 요구 (템플릿 방식) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().authenticated() + ) + + // 6. [사용자 정의] 리소스 서버 설정 (JWT 검증) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) + + // 7. [사용자 정의] 인증 실패 시 커스텀 EntryPoint 사용 (템플릿 구조 + 사용자 로직) + // (템플릿의 /login 리디렉션 대신, 기존의 provider 분기 로직을 사용) + .exceptionHandling((exceptions) -> exceptions + .defaultAuthenticationEntryPointFor( + entryPoint, // 사용자의 커스텀 EntryPoint + rq -> rq.getRequestURI().startsWith("/oauth2/authorize") // 사용자의 커스텀 Predicate + ) + ) + + // 8. [사용자 정의] CSRF 설정 + .csrf(csrf -> csrf.ignoringRequestMatchers("/oauth2/**", "/.well-known/**")); + + return http.build(); } @Bean @@ -104,6 +135,7 @@ public RegisteredClientRepository registeredClientRepository() { type.add(AuthorizationGrantType.AUTHORIZATION_CODE); type.add(AuthorizationGrantType.REFRESH_TOKEN); type.add(KakaoGrantType.KAKAO_ACCESS_TOKEN); + type.add(AppleGrantType.APPLE_IDENTITY_TOKEN); }) .redirectUris(uri -> { uri.addAll(leenk.getRedirectUris()); @@ -203,6 +235,20 @@ KakaoAuthenticationProvider kakaoProvider( kakaoAuthService, userGetService, authorizationService, tokenGenerator); } + @Bean + AppleIdentityTokenAuthenticationConverter appleConverter() { + return new AppleIdentityTokenAuthenticationConverter(); + } + + @Bean + AppleAuthenticationProvider appleProvider( + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator tokenGenerator) { + + return new AppleAuthenticationProvider( + appleAuthService, userGetService, authorizationService, tokenGenerator); + } + private RSAKey loadRsaKeyFromString() { try { return new RSAKey.Builder(publicKey) diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java new file mode 100644 index 00000000..4dcab1ce --- /dev/null +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java @@ -0,0 +1,73 @@ +package leets.weeth.global.sas.config.grant; + +import leets.weeth.domain.user.domain.entity.User; +import leets.weeth.domain.user.domain.service.UserGetService; +import leets.weeth.global.auth.apple.AppleAuthService; +import leets.weeth.global.auth.apple.dto.AppleUserInfo; +import leets.weeth.global.sas.application.exception.AppleLoginException; +import leets.weeth.global.sas.application.exception.UserInActiveException; +import leets.weeth.global.sas.application.exception.UserNotFoundException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2Token; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; +import org.springframework.stereotype.Component; + +@Component +public class AppleAuthenticationProvider extends CustomAuthenticationProvider { + + private final AppleAuthService appleAuthService; + private final UserGetService userGetService; + + public AppleAuthenticationProvider( + AppleAuthService appleAuthService, + UserGetService userGetService, + OAuth2AuthorizationService authorizationService, + OAuth2TokenGenerator tokenGenerator + ) { + super(authorizationService, tokenGenerator); + this.appleAuthService = appleAuthService; + this.userGetService = userGetService; + } + + @Override + protected AuthorizationGrantType getGrantTokenType() { + return AppleGrantType.APPLE_IDENTITY_TOKEN; + } + + @Override + protected Class getAuthenticationClass() { + return AppleIdentityTokenAuthenticationToken.class; + } + + @Override + protected String extractAccessToken(Authentication authentication) { + AppleIdentityTokenAuthenticationToken grantAuth = + (AppleIdentityTokenAuthenticationToken) authentication; + return grantAuth.getAppleIdentityToken(); + } + + @Override + protected AppleUserInfo getUserInfo(String identityToken) { + try { + // Identity Token 검증 및 사용자 정보 추출 + return appleAuthService.verifyAndDecodeIdToken(identityToken); + } catch (Exception e) { + throw new AppleLoginException(e.getMessage()); + } + } + + @Override + protected User getOrLoadUser(AppleUserInfo userInfo) { + String appleId = userInfo.appleId(); + User user = userGetService.findByAppleId(appleId) + .orElseThrow(UserNotFoundException::new); + + if (user.isInactive()) { + throw new UserInActiveException(); + } + + return user; + } +} \ No newline at end of file diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java new file mode 100644 index 00000000..d6a81801 --- /dev/null +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java @@ -0,0 +1,10 @@ +package leets.weeth.global.sas.config.grant; + +import org.springframework.security.oauth2.core.AuthorizationGrantType; + +public final class AppleGrantType { + public static final AuthorizationGrantType APPLE_IDENTITY_TOKEN = + new AuthorizationGrantType("apple_identity_token"); + + private AppleGrantType() {} +} \ No newline at end of file diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java new file mode 100644 index 00000000..bc0fe593 --- /dev/null +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java @@ -0,0 +1,37 @@ +package leets.weeth.global.sas.config.grant; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.util.StringUtils; + +import java.util.HashMap; + +public class AppleIdentityTokenAuthenticationConverter implements AuthenticationConverter { + + @Override + public Authentication convert(HttpServletRequest request) { + if (!AppleGrantType.APPLE_IDENTITY_TOKEN.getValue() + .equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE))) { + return null; + } + + String identityToken = request.getParameter("identity_token"); + if (!StringUtils.hasText(identityToken)) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); + } + + Authentication clientPrincipal = (Authentication) request.getUserPrincipal(); + + var additional = new HashMap(); + request.getParameterMap().forEach((k, v) -> { + if (!OAuth2ParameterNames.GRANT_TYPE.equals(k) && !"identity_token".equals(k)) + additional.put(k, v[0]); + }); + + return new AppleIdentityTokenAuthenticationToken(identityToken, clientPrincipal, additional); + } +} \ No newline at end of file diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java new file mode 100644 index 00000000..d76c4bf4 --- /dev/null +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java @@ -0,0 +1,22 @@ +package leets.weeth.global.sas.config.grant; + +import lombok.Getter; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; + +import java.util.Map; + +@Getter +public class AppleIdentityTokenAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken { + + private final String appleIdentityToken; + + public AppleIdentityTokenAuthenticationToken( + String appleIdentityToken, + Authentication clientPrincipal, + Map additionalParameters) { + super(new AuthorizationGrantType("apple_identity_token"), clientPrincipal, additionalParameters); + this.appleIdentityToken = appleIdentityToken; + } +} \ No newline at end of file From 272771d4880279197b6b7d4cd340f1ef57c976b8 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 18:47:19 +0900 Subject: [PATCH 104/114] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=84=B8=EC=85=98=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/AppleLoginException.java | 10 +++++++ .../application/exception/ErrorMessage.java | 3 +- .../sas/application/usecase/AuthUsecase.java | 30 +++++++++++++++++++ .../sas/presentation/AuthController.java | 19 +++++++++++- 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java diff --git a/src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java b/src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java new file mode 100644 index 00000000..cea5f658 --- /dev/null +++ b/src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java @@ -0,0 +1,10 @@ +package leets.weeth.global.sas.application.exception; + +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; + +public class AppleLoginException extends OAuth2AuthenticationException { + public AppleLoginException(String message) { + super(new OAuth2Error(ErrorMessage.APPLE_AUTH_ERROR.getCode(), message, null)); + } +} \ No newline at end of file diff --git a/src/main/java/leets/weeth/global/sas/application/exception/ErrorMessage.java b/src/main/java/leets/weeth/global/sas/application/exception/ErrorMessage.java index 82d24c54..a897fa5d 100644 --- a/src/main/java/leets/weeth/global/sas/application/exception/ErrorMessage.java +++ b/src/main/java/leets/weeth/global/sas/application/exception/ErrorMessage.java @@ -9,7 +9,8 @@ public enum ErrorMessage { USER_INACTIVE("WAE-001", "가입 승인이 허가되지 않은 계정입니다."), USER_NOT_FOUND("WAE-002", "존재하지 않는 유저입니다."), - KAKAO_AUTH_ERROR("WAE-003", "카카오 로그인 예외입니다."); + KAKAO_AUTH_ERROR("WAE-003", "카카오 로그인 예외입니다."), + APPLE_AUTH_ERROR("WAE-004", "애플 로그인 예외입니다."); private final String code; private final String description; diff --git a/src/main/java/leets/weeth/global/sas/application/usecase/AuthUsecase.java b/src/main/java/leets/weeth/global/sas/application/usecase/AuthUsecase.java index 22de81b8..a110df15 100644 --- a/src/main/java/leets/weeth/global/sas/application/usecase/AuthUsecase.java +++ b/src/main/java/leets/weeth/global/sas/application/usecase/AuthUsecase.java @@ -6,6 +6,9 @@ import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.service.UserCardinalGetService; import leets.weeth.domain.user.domain.service.UserGetService; +import leets.weeth.global.auth.apple.AppleAuthService; +import leets.weeth.global.auth.apple.dto.AppleTokenResponse; +import leets.weeth.global.auth.apple.dto.AppleUserInfo; import leets.weeth.global.auth.jwt.service.JwtService; import leets.weeth.global.auth.kakao.KakaoAuthService; import leets.weeth.global.auth.kakao.dto.KakaoTokenResponse; @@ -22,6 +25,7 @@ public class AuthUsecase { private final KakaoAuthService kakaoAuthService; + private final AppleAuthService appleAuthService; private final UserGetService userGetService; private final JwtService jwtService; private final UserCardinalGetService userCardinalGetService; @@ -51,6 +55,32 @@ public User login(String authCode) { return user; } + /* + 필요 없음 + */ + public User appleLogin(String authCode, String idToken) { + AppleTokenResponse tokenResponse = appleAuthService.getAppleToken(authCode); + + // ID Token 사용 + String token = idToken != null ? idToken : tokenResponse.id_token(); + AppleUserInfo userInfo = appleAuthService.verifyAndDecodeIdToken(token); + + String appleId = userInfo.appleId(); + Optional optionalUser = userGetService.findByAppleId(appleId); + + if (optionalUser.isEmpty()) { + throw new UserNotFoundException(); // -> Weeth 회원가입 페이지로 리다이렉트 + } + + User user = optionalUser.get(); + + if (user.isInactive()) { + throw new UserInActiveException(); // -> 가입 승인 대기 + } + + return user; + } + public OauthUserInfoResponse userInfo(String accessToken) { String token = accessToken.substring(7); diff --git a/src/main/java/leets/weeth/global/sas/presentation/AuthController.java b/src/main/java/leets/weeth/global/sas/presentation/AuthController.java index 6329292f..8627b09f 100644 --- a/src/main/java/leets/weeth/global/sas/presentation/AuthController.java +++ b/src/main/java/leets/weeth/global/sas/presentation/AuthController.java @@ -7,7 +7,6 @@ import leets.weeth.global.sas.application.dto.OauthUserInfoResponse; import leets.weeth.global.sas.application.usecase.AuthUsecase; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -47,6 +46,24 @@ public void kakaoCallback(@RequestParam String code, savedRequestHandler.onAuthenticationSuccess(request, response, auth); } + @GetMapping("/apple/oauth") + public void appleCallback(@RequestParam String code, + @RequestParam(required = false) String id_token, + HttpServletRequest request, + HttpServletResponse response) throws Exception { + + User findUser = authUsecase.appleLogin(code, id_token); + + Authentication auth = new UsernamePasswordAuthenticationToken(SecurityUser.from(findUser), null, List.of(new SimpleGrantedAuthority(findUser.getRole().name()))); + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(auth); + SecurityContextHolder.setContext(context); + securityContextRepository.saveContext(context, request, response); + + savedRequestHandler.onAuthenticationSuccess(request, response, auth); + } + @GetMapping("/user/me") public OauthUserInfoResponse userInfo(@RequestHeader("Authorization") String accessToken) { return authUsecase.userInfo(accessToken); From ed9ea5ec3bb846b2d072e46333e5ce186281a67f Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 18:48:06 +0900 Subject: [PATCH 105/114] =?UTF-8?q?feat:=20=EC=95=A0=ED=94=8C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=8F=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/UserRequestDto.java | 6 ++- .../dto/response/UserResponseDto.java | 1 + .../user/application/mapper/UserMapper.java | 22 ++++++++ .../user/application/usecase/UserUseCase.java | 4 ++ .../application/usecase/UserUseCaseImpl.java | 53 +++++++++++++++++++ .../weeth/domain/user/domain/entity/User.java | 6 +++ .../domain/repository/UserRepository.java | 2 + .../user/domain/service/UserGetService.java | 4 ++ .../user/presentation/UserController.java | 14 +++++ 9 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/main/java/leets/weeth/domain/user/application/dto/request/UserRequestDto.java b/src/main/java/leets/weeth/domain/user/application/dto/request/UserRequestDto.java index 2cfdb8cf..30ac0336 100644 --- a/src/main/java/leets/weeth/domain/user/application/dto/request/UserRequestDto.java +++ b/src/main/java/leets/weeth/domain/user/application/dto/request/UserRequestDto.java @@ -1,5 +1,6 @@ package leets.weeth.domain.user.application.dto.request; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -27,7 +28,10 @@ public record SignUp( } public record Register( - @NotNull Long kakaoId, + @Schema(description = "kakao로 회원가입 하는 경우") + Long kakaoId, + @Schema(description = "애플로 회원가입 하는 경우 - Apple OAuth authCode") + String appleAuthCode, @NotBlank String name, @NotBlank String studentId, @NotBlank String email, diff --git a/src/main/java/leets/weeth/domain/user/application/dto/response/UserResponseDto.java b/src/main/java/leets/weeth/domain/user/application/dto/response/UserResponseDto.java index 8f38c0ed..5a76d645 100644 --- a/src/main/java/leets/weeth/domain/user/application/dto/response/UserResponseDto.java +++ b/src/main/java/leets/weeth/domain/user/application/dto/response/UserResponseDto.java @@ -13,6 +13,7 @@ public class UserResponseDto { public record SocialLoginResponse( Long id, Long kakaoId, + String appleIdToken, LoginStatus status, String accessToken, String refreshToken diff --git a/src/main/java/leets/weeth/domain/user/application/mapper/UserMapper.java b/src/main/java/leets/weeth/domain/user/application/mapper/UserMapper.java index e41866ec..046a0f5f 100644 --- a/src/main/java/leets/weeth/domain/user/application/mapper/UserMapper.java +++ b/src/main/java/leets/weeth/domain/user/application/mapper/UserMapper.java @@ -49,11 +49,15 @@ public interface UserMapper { @Mapping(target = "status", expression = "java(LoginStatus.LOGIN)"), @Mapping(target = "id", source = "user.id"), @Mapping(target = "kakaoId", source = "user.kakaoId"), + @Mapping(target = "appleIdToken", expression = "java(null)") }) SocialLoginResponse toLoginResponse(User user, JwtDto dto); @Mappings({ @Mapping(target = "status", expression = "java(LoginStatus.INTEGRATE)"), + @Mapping(target = "appleIdToken", expression = "java(null)"), + @Mapping(target = "accessToken", expression = "java(null)"), + @Mapping(target = "refreshToken", expression = "java(null)") }) SocialLoginResponse toIntegrateResponse(Long kakaoId); @@ -66,6 +70,24 @@ public interface UserMapper { @Mapping(target = "cardinals", expression = "java( toCardinalNumbers(userCardinals) )") UserResponseDto.UserInfo toUserInfoDto(User user, List userCardinals); + @Mappings({ + @Mapping(target = "status", expression = "java(LoginStatus.LOGIN)"), + @Mapping(target = "id", source = "user.id"), + @Mapping(target = "appleIdToken", expression = "java(null)"), + @Mapping(target = "kakaoId", expression = "java(null)") + }) + SocialLoginResponse toAppleLoginResponse(User user, JwtDto dto); + + @Mappings({ + @Mapping(target = "status", expression = "java(LoginStatus.INTEGRATE)"), + @Mapping(target = "id", expression = "java(null)"), + @Mapping(target = "appleIdToken", source = "appleIdToken"), + @Mapping(target = "kakaoId", expression = "java(null)"), + @Mapping(target = "accessToken", expression = "java(null)"), + @Mapping(target = "refreshToken", expression = "java(null)") + }) + SocialLoginResponse toAppleIntegrateResponse(String appleIdToken); + default String toString(Department department) { return department.getValue(); } diff --git a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCase.java b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCase.java index ae71bf99..e1164291 100644 --- a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCase.java +++ b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCase.java @@ -37,4 +37,8 @@ public interface UserUseCase { List searchUser(String keyword); + SocialLoginResponse appleLogin(Login dto); + + void appleRegister(Register dto); + } diff --git a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java index 2cbe3ecd..b620c93d 100644 --- a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java @@ -11,6 +11,8 @@ import leets.weeth.domain.user.domain.entity.User; import leets.weeth.domain.user.domain.entity.UserCardinal; import leets.weeth.domain.user.domain.service.*; +import leets.weeth.global.auth.apple.dto.AppleTokenResponse; +import leets.weeth.global.auth.apple.dto.AppleUserInfo; import leets.weeth.global.auth.jwt.application.dto.JwtDto; import leets.weeth.global.auth.jwt.application.usecase.JwtManageUseCase; import leets.weeth.global.auth.kakao.KakaoAuthService; @@ -44,6 +46,7 @@ public class UserUseCaseImpl implements UserUseCase { private final UserGetService userGetService; private final UserUpdateService userUpdateService; private final KakaoAuthService kakaoAuthService; + private final leets.weeth.global.auth.apple.AppleAuthService appleAuthService; private final CardinalGetService cardinalGetService; private final UserCardinalSaveService userCardinalSaveService; private final UserCardinalGetService userCardinalGetService; @@ -238,4 +241,54 @@ private UserCardinalDto getUserCardinalDto(Long userId) { return cardinalMapper.toUserCardinalDto(user, userCardinals); } + + @Override + @Transactional(readOnly = true) + public SocialLoginResponse appleLogin(Login dto) { + // Apple Token 요청 및 유저 정보 요청 + AppleTokenResponse tokenResponse = appleAuthService.getAppleToken(dto.authCode()); + AppleUserInfo userInfo = appleAuthService.verifyAndDecodeIdToken(tokenResponse.id_token()); + + String appleIdToken = tokenResponse.id_token(); + String appleId = userInfo.appleId(); + + Optional optionalUser = userGetService.findByAppleId(appleId); + + //todo: 추후 애플 로그인 연동을 위해 appleIdToken을 반환 + // 애플 로그인 연동 API 요청시 appleIdToken을 함께 넣어주면 그때 디코딩해서 appleId를 추출 + if (optionalUser.isEmpty()) { + return mapper.toAppleIntegrateResponse(appleIdToken); + } + + User user = optionalUser.get(); + if (user.isInactive()) { + throw new UserInActiveException(); + } + + JwtDto token = jwtManageUseCase.create(user.getId(), user.getEmail(), user.getRole()); + return mapper.toAppleLoginResponse(user, token); + } + + @Override + @Transactional + public void appleRegister(Register dto) { + validate(dto); + + // Apple authCode로 토큰 교환 후 ID Token 검증 및 사용자 정보 추출 + AppleTokenResponse tokenResponse = appleAuthService.getAppleToken(dto.appleAuthCode()); + AppleUserInfo appleUserInfo = appleAuthService.verifyAndDecodeIdToken(tokenResponse.id_token()); + + Cardinal cardinal = cardinalGetService.findByUserSide(dto.cardinal()); + + User user = mapper.from(dto); + // Apple ID 설정 + user.addAppleId(appleUserInfo.appleId()); + // dev 전용: 바로 ACTIVE 상태로 설정 + user.accept(); + + UserCardinal userCardinal = new UserCardinal(user, cardinal); + + userSaveService.save(user); + userCardinalSaveService.save(userCardinal); + } } diff --git a/src/main/java/leets/weeth/domain/user/domain/entity/User.java b/src/main/java/leets/weeth/domain/user/domain/entity/User.java index 22e020be..2a66abcb 100644 --- a/src/main/java/leets/weeth/domain/user/domain/entity/User.java +++ b/src/main/java/leets/weeth/domain/user/domain/entity/User.java @@ -44,6 +44,8 @@ public class User extends BaseEntity { private Long kakaoId; + private String appleId; + private String name; private String email; @@ -94,6 +96,10 @@ public void addKakaoId(long kakaoId) { this.kakaoId = kakaoId; } + public void addAppleId(String appleId) { + this.appleId = appleId; + } + public void leave() { this.status = Status.LEFT; } diff --git a/src/main/java/leets/weeth/domain/user/domain/repository/UserRepository.java b/src/main/java/leets/weeth/domain/user/domain/repository/UserRepository.java index 23c61e5a..8881f0a4 100644 --- a/src/main/java/leets/weeth/domain/user/domain/repository/UserRepository.java +++ b/src/main/java/leets/weeth/domain/user/domain/repository/UserRepository.java @@ -18,6 +18,8 @@ public interface UserRepository extends JpaRepository { Optional findByKakaoId(long kakaoId); + Optional findByAppleId(String appleId); + ListfindAllByNameContainingAndStatus(String name, Status status); boolean existsByEmail(String email); diff --git a/src/main/java/leets/weeth/domain/user/domain/service/UserGetService.java b/src/main/java/leets/weeth/domain/user/domain/service/UserGetService.java index 15fe07f9..1498f0a6 100644 --- a/src/main/java/leets/weeth/domain/user/domain/service/UserGetService.java +++ b/src/main/java/leets/weeth/domain/user/domain/service/UserGetService.java @@ -33,6 +33,10 @@ public Optional find(long kakaoId){ return userRepository.findByKakaoId(kakaoId); } + public Optional findByAppleId(String appleId){ + return userRepository.findByAppleId(appleId); + } + public List search(String keyword) { return userRepository.findAllByNameContainingAndStatus(keyword, Status.ACTIVE); } diff --git a/src/main/java/leets/weeth/domain/user/presentation/UserController.java b/src/main/java/leets/weeth/domain/user/presentation/UserController.java index 269293cd..9c131428 100644 --- a/src/main/java/leets/weeth/domain/user/presentation/UserController.java +++ b/src/main/java/leets/weeth/domain/user/presentation/UserController.java @@ -68,6 +68,20 @@ public CommonResponse integrate(@RequestBody @Valid NormalL return CommonResponse.createSuccess(SOCIAL_INTEGRATE_SUCCESS.getMessage(), userUseCase.integrate(dto)); } + @PostMapping("/apple/login") + @Operation(summary = "애플 소셜 로그인 API") + public CommonResponse appleLogin(@RequestBody @Valid Login dto) { + SocialLoginResponse response = userUseCase.appleLogin(dto); + return CommonResponse.createSuccess(SOCIAL_LOGIN_SUCCESS.getMessage(), response); + } + + @PostMapping("/apple/register") + @Operation(summary = "애플 소셜 회원가입 (dev 전용 - 바로 ACTIVE)") + public CommonResponse appleRegister(@RequestBody @Valid Register dto) { + userUseCase.appleRegister(dto); + return CommonResponse.createSuccess(USER_APPLY_SUCCESS.getMessage()); + } + @GetMapping("/email") @Operation(summary = "이메일 중복 확인") public CommonResponse checkEmail(@RequestParam String email) { From 49592a2947ab29a8afbc2f8c10fcfa1f70d3e6c0 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 18:48:21 +0900 Subject: [PATCH 106/114] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=ED=95=B4?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/leets/weeth/global/config/SecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/leets/weeth/global/config/SecurityConfig.java b/src/main/java/leets/weeth/global/config/SecurityConfig.java index 6f33c2a0..a0233f0d 100644 --- a/src/main/java/leets/weeth/global/config/SecurityConfig.java +++ b/src/main/java/leets/weeth/global/config/SecurityConfig.java @@ -74,9 +74,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests( authorize -> authorize - .requestMatchers("/api/v1/users/kakao/login", "api/v1/users/kakao/register", "api/v1/users/kakao/link", "/api/v1/users/apply", "/api/v1/users/email", "/api/v1/users/refresh").permitAll() + .requestMatchers("/api/v1/users/kakao/login", "api/v1/users/kakao/register", "api/v1/users/kakao/link", "/api/v1/users/apple/login", "/api/v1/users/apple/register", "/api/v1/users/apply", "/api/v1/users/email", "/api/v1/users/refresh").permitAll() .requestMatchers("/health-check").permitAll() - .requestMatchers("/oauth2/**", "/.well-known/**", "/kakao/oauth").permitAll() + .requestMatchers("/oauth2/**", "/.well-known/**", "/kakao/oauth", "/apple/oauth").permitAll() .requestMatchers("/admin", "/admin/login", "/admin/account", "/admin/meeting", "/admin/member", "/admin/penalty", "/js/**", "/img/**", "/scss/**", "/vendor/**").permitAll() // 스웨거 경로 From 5db0cc4da2d2e638b8051dcd7612078bf676085b Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 19:18:53 +0900 Subject: [PATCH 107/114] =?UTF-8?q?refactor:=20=EC=95=A0=ED=94=8C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20converter=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/UserUseCaseImpl.java | 6 +++-- .../mapper/OAuth2AuthorizationConverter.java | 24 +++++++++++++++++++ .../RedisOAuth2AuthorizationService.java | 10 ++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java index b620c93d..f424f8f2 100644 --- a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java @@ -250,6 +250,7 @@ public SocialLoginResponse appleLogin(Login dto) { AppleUserInfo userInfo = appleAuthService.verifyAndDecodeIdToken(tokenResponse.id_token()); String appleIdToken = tokenResponse.id_token(); + log.info("appleIdToken: {}", appleIdToken); String appleId = userInfo.appleId(); Optional optionalUser = userGetService.findByAppleId(appleId); @@ -283,12 +284,13 @@ public void appleRegister(Register dto) { User user = mapper.from(dto); // Apple ID 설정 user.addAppleId(appleUserInfo.appleId()); - // dev 전용: 바로 ACTIVE 상태로 설정 - user.accept(); UserCardinal userCardinal = new UserCardinal(user, cardinal); userSaveService.save(user); userCardinalSaveService.save(userCardinal); + + // dev 전용: 바로 ACTIVE 상태로 설정 (save 이후 호출하여 @PrePersist 이후 더티 체킹으로 반영) + user.accept(); } } diff --git a/src/main/java/leets/weeth/global/sas/application/mapper/OAuth2AuthorizationConverter.java b/src/main/java/leets/weeth/global/sas/application/mapper/OAuth2AuthorizationConverter.java index 169d157e..1f42d123 100644 --- a/src/main/java/leets/weeth/global/sas/application/mapper/OAuth2AuthorizationConverter.java +++ b/src/main/java/leets/weeth/global/sas/application/mapper/OAuth2AuthorizationConverter.java @@ -1,6 +1,7 @@ package leets.weeth.global.sas.application.mapper; +import leets.weeth.global.sas.config.grant.AppleGrantType; import leets.weeth.global.sas.config.grant.KakaoGrantType; import leets.weeth.global.sas.domain.entity.*; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -33,6 +34,10 @@ public static OAuth2AuthorizationGrantAuthorization convertOAuth2AuthorizationGr return convertKakaoAuthorizationGrantAuthorization(authorization); } + if (AppleGrantType.APPLE_IDENTITY_TOKEN.equals(grantType)) { + return convertAppleAuthorizationGrantAuthorization(authorization); + } + return null; } @@ -55,6 +60,25 @@ public static OAuth2AuthorizationGrantAuthorization convertOAuth2AuthorizationGr .build(); } + private static OidcAuthorizationCodeGrantAuthorization + convertAppleAuthorizationGrantAuthorization(OAuth2Authorization authorization) { + + AccessToken accessToken = extractAccessToken(authorization); + RefreshToken refreshToken = extractRefreshToken(authorization); + IdToken idToken = extractIdToken(authorization); + + return OidcAuthorizationCodeGrantAuthorization.builder() + .id(authorization.getId()) + .registeredClientId(authorization.getRegisteredClientId()) + .principalName(authorization.getPrincipalName()) + .authorizedScopes(authorization.getAuthorizedScopes()) + .accessToken(accessToken) + .refreshToken(refreshToken) + .idToken(idToken) + .principal(authorization.getAttribute(Principal.class.getName())) + .build(); + } + static OidcAuthorizationCodeGrantAuthorization convertOidcAuthorizationCodeGrantAuthorization(OAuth2Authorization authorization) { AuthorizationCode authorizationCode = extractAuthorizationCode(authorization); AccessToken accessToken = extractAccessToken(authorization); diff --git a/src/main/java/leets/weeth/global/sas/domain/service/RedisOAuth2AuthorizationService.java b/src/main/java/leets/weeth/global/sas/domain/service/RedisOAuth2AuthorizationService.java index 4909fc5a..a361ed0b 100644 --- a/src/main/java/leets/weeth/global/sas/domain/service/RedisOAuth2AuthorizationService.java +++ b/src/main/java/leets/weeth/global/sas/domain/service/RedisOAuth2AuthorizationService.java @@ -36,9 +36,15 @@ public void save(OAuth2Authorization authorization) { // ttl 설정 if (entity.getAccessToken() == null && entity instanceof OAuth2AuthorizationCodeGrantAuthorization codeGrant) { - entity.updateExpire(calculateTtlSeconds(((OAuth2AuthorizationCodeGrantAuthorization) entity).getAuthorizationCode().getExpiresAt())); - } else { + entity.updateExpire(calculateTtlSeconds(codeGrant.getAuthorizationCode().getExpiresAt())); + } else if (entity.getRefreshToken() != null) { entity.updateExpire(calculateTtlSeconds(entity.getRefreshToken().getExpiresAt())); + } else if (entity.getAccessToken() != null) { + // refresh token이 없으면 access token의 만료 시간 사용 + entity.updateExpire(calculateTtlSeconds(entity.getAccessToken().getExpiresAt())); + } else { + // access token도 없으면 기본값으로 1시간 설정 + entity.updateExpire(3600L); } this.authorizationGrantAuthorizationRepository.save(entity); From e78b9a9485c3d35a68ada49232398baa1137b2fa Mon Sep 17 00:00:00 2001 From: hyxklee Date: Mon, 10 Nov 2025 19:38:39 +0900 Subject: [PATCH 108/114] =?UTF-8?q?refactor:=20=ED=82=A4=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/auth/apple/AppleAuthService.java | 23 +++++++++++++++---- src/main/resources/application.yml | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java b/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java index bdf690fd..9e37ba89 100644 --- a/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java +++ b/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java @@ -16,8 +16,11 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClient; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.math.BigInteger; -import java.nio.file.Files; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; @@ -134,10 +137,9 @@ public AppleUserInfo verifyAndDecodeIdToken(String idToken) { * ES256 알고리즘으로 JWT 생성 (p8 키 파일 사용) */ private String generateClientSecret() { - try { + try (InputStream inputStream = getInputStream(privateKeyPath)) { // p8 파일에서 Private Key 읽기 - ClassPathResource resource = new ClassPathResource(privateKeyPath); - String privateKeyContent = new String(Files.readAllBytes(resource.getFile().toPath())); + String privateKeyContent = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); // PEM 형식의 헤더/푸터 제거 privateKeyContent = privateKeyContent @@ -174,6 +176,19 @@ private String generateClientSecret() { } } + /** + * 파일 경로에서 InputStream 가져오기 + * 절대 경로면 파일 시스템에서, 상대 경로면 classpath에서 읽음 + */ + private InputStream getInputStream(String path) throws IOException { + // 절대 경로인 경우 파일 시스템에서 읽기 + if (path.startsWith("/") || path.matches("^[A-Za-z]:.*")) { + return new FileInputStream(path); + } + // 상대 경로는 classpath에서 읽기 + return new ClassPathResource(path).getInputStream(); + } + /** * 애플 공개키로부터 PublicKey 객체 생성 */ diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7b6908f1..94b3fd9b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -34,7 +34,7 @@ auth: redirect_uri: ${APPLE_REDIRECT_URI} token_uri: https://appleid.apple.com/auth/token keys_uri: https://appleid.apple.com/auth/keys - private_key_path: AuthKey_5JL5Z7K6J7.p8 + private_key_path: ${APPLE_PRIVATE_KEY_PATH} jwt: private-key: ${JWT_PRIVATE_KEY} public-key: ${JWT_PUBLIC_KEY} From 5b4e56e5f89e1e824c3f241d168e52bc5fcc4934 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Tue, 11 Nov 2025 20:50:41 +0900 Subject: [PATCH 109/114] =?UTF-8?q?refactor:=20=EA=B0=9C=ED=96=89=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 --- .../global/sas/config/grant/AppleAuthenticationProvider.java | 2 +- .../leets/weeth/global/sas/config/grant/AppleGrantType.java | 2 +- .../config/grant/AppleIdentityTokenAuthenticationConverter.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java index 4dcab1ce..d18db726 100644 --- a/src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleAuthenticationProvider.java @@ -70,4 +70,4 @@ protected User getOrLoadUser(AppleUserInfo userInfo) { return user; } -} \ No newline at end of file +} diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java index d6a81801..78155dfa 100644 --- a/src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleGrantType.java @@ -7,4 +7,4 @@ public final class AppleGrantType { new AuthorizationGrantType("apple_identity_token"); private AppleGrantType() {} -} \ No newline at end of file +} diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java index bc0fe593..dd9c5cdc 100644 --- a/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationConverter.java @@ -34,4 +34,4 @@ public Authentication convert(HttpServletRequest request) { return new AppleIdentityTokenAuthenticationToken(identityToken, clientPrincipal, additional); } -} \ No newline at end of file +} From 5d6686e8e58a4fd7b4ea2b1acfadd814d7215572 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Tue, 11 Nov 2025 20:50:48 +0900 Subject: [PATCH 110/114] =?UTF-8?q?refactor:=20=EA=B0=9C=ED=96=89=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 --- .../global/sas/application/exception/AppleLoginException.java | 2 +- .../sas/config/grant/AppleIdentityTokenAuthenticationToken.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java b/src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java index cea5f658..6e3bd0aa 100644 --- a/src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java +++ b/src/main/java/leets/weeth/global/sas/application/exception/AppleLoginException.java @@ -7,4 +7,4 @@ public class AppleLoginException extends OAuth2AuthenticationException { public AppleLoginException(String message) { super(new OAuth2Error(ErrorMessage.APPLE_AUTH_ERROR.getCode(), message, null)); } -} \ No newline at end of file +} diff --git a/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java index d76c4bf4..02947407 100644 --- a/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java +++ b/src/main/java/leets/weeth/global/sas/config/grant/AppleIdentityTokenAuthenticationToken.java @@ -19,4 +19,4 @@ public AppleIdentityTokenAuthenticationToken( super(new AuthorizationGrantType("apple_identity_token"), clientPrincipal, additionalParameters); this.appleIdentityToken = appleIdentityToken; } -} \ No newline at end of file +} From c2b079296bc36884d03c6cdefdc2f5c5d633535a Mon Sep 17 00:00:00 2001 From: hyxklee Date: Tue, 11 Nov 2025 20:50:58 +0900 Subject: [PATCH 111/114] =?UTF-8?q?refactor:=20=EC=9C=A0=EB=8B=88=ED=81=AC?= =?UTF-8?q?=20=ED=82=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/domain/user/domain/entity/User.java | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/main/java/leets/weeth/domain/user/domain/entity/User.java b/src/main/java/leets/weeth/domain/user/domain/entity/User.java index 2a66abcb..9b5a137e 100644 --- a/src/main/java/leets/weeth/domain/user/domain/entity/User.java +++ b/src/main/java/leets/weeth/domain/user/domain/entity/User.java @@ -1,20 +1,6 @@ package leets.weeth.domain.user.domain.entity; -import static leets.weeth.domain.user.application.dto.request.UserRequestDto.Update; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; -import jakarta.persistence.PrePersist; -import jakarta.persistence.Table; -import java.util.ArrayList; -import java.util.List; +import jakarta.persistence.*; import leets.weeth.domain.attendance.domain.entity.Attendance; import leets.weeth.domain.board.domain.entity.enums.Part; import leets.weeth.domain.user.domain.entity.enums.Department; @@ -29,6 +15,11 @@ import lombok.experimental.SuperBuilder; import org.springframework.security.crypto.password.PasswordEncoder; +import java.util.ArrayList; +import java.util.List; + +import static leets.weeth.domain.user.application.dto.request.UserRequestDto.Update; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -42,8 +33,10 @@ public class User extends BaseEntity { @Column(name = "user_id") private Long id; + @Column(unique = true) private Long kakaoId; + @Column(unique = true) private String appleId; private String name; From ecda68d1e9e6abfc0dc75063a1d9601026113779 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Tue, 11 Nov 2025 20:56:12 +0900 Subject: [PATCH 112/114] =?UTF-8?q?refactor:=20dev,=20local=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=EC=84=9C=EB=A7=8C=20=EB=B0=94=EB=A1=9C=20?= =?UTF-8?q?=EC=8A=B9=EC=9D=B8=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/UserUseCaseImpl.java | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java index f424f8f2..a3205fc3 100644 --- a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java @@ -20,6 +20,7 @@ import leets.weeth.global.auth.kakao.dto.KakaoUserInfoResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -27,10 +28,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import static leets.weeth.domain.user.application.dto.request.UserRequestDto.*; @@ -54,6 +52,7 @@ public class UserUseCaseImpl implements UserUseCase { private final UserMapper mapper; private final CardinalMapper cardinalMapper; private final PasswordEncoder passwordEncoder; + private final Environment environment; @Override @Transactional(readOnly = true) @@ -250,7 +249,6 @@ public SocialLoginResponse appleLogin(Login dto) { AppleUserInfo userInfo = appleAuthService.verifyAndDecodeIdToken(tokenResponse.id_token()); String appleIdToken = tokenResponse.id_token(); - log.info("appleIdToken: {}", appleIdToken); String appleId = userInfo.appleId(); Optional optionalUser = userGetService.findByAppleId(appleId); @@ -273,6 +271,7 @@ public SocialLoginResponse appleLogin(Login dto) { @Override @Transactional public void appleRegister(Register dto) { + isDevEnvironment(); validate(dto); // Apple authCode로 토큰 교환 후 ID Token 검증 및 사용자 정보 추출 @@ -290,7 +289,27 @@ public void appleRegister(Register dto) { userSaveService.save(user); userCardinalSaveService.save(userCardinal); - // dev 전용: 바로 ACTIVE 상태로 설정 (save 이후 호출하여 @PrePersist 이후 더티 체킹으로 반영) - user.accept(); + // dev 환경에서만 바로 ACTIVE 상태로 설정 + if (isDevEnvironment()) { + log.info("dev 환경 감지: 사용자 자동 승인 처리 (userId: {})", user.getId()); + user.accept(); + } + } + + /** + * 현재 환경이 dev 프로파일인지 확인 + * @return dev 프로파일이 활성화되어 있으면 true + */ + private boolean isDevEnvironment() { + String[] activeProfiles = environment.getActiveProfiles(); + for (String profile : activeProfiles) { + if ("dev".equals(profile)) { + return true; + } + if ("local".equals(profile)) { + return true; + } + } + return false; } } From 559e4477af587efd681d33518144ba797cfacbff Mon Sep 17 00:00:00 2001 From: hyxklee Date: Tue, 11 Nov 2025 21:07:55 +0900 Subject: [PATCH 113/114] =?UTF-8?q?refactor:=20=EC=BB=A4=EC=8A=A4=ED=85=80?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=20=EB=8D=98=EC=A7=80=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/global/auth/apple/AppleAuthService.java | 11 ++++++----- .../apple/exception/AppleAuthenticationException.java | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 src/main/java/leets/weeth/global/auth/apple/exception/AppleAuthenticationException.java diff --git a/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java b/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java index 9e37ba89..160788da 100644 --- a/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java +++ b/src/main/java/leets/weeth/global/auth/apple/AppleAuthService.java @@ -7,6 +7,7 @@ import leets.weeth.global.auth.apple.dto.ApplePublicKeys; import leets.weeth.global.auth.apple.dto.AppleTokenResponse; import leets.weeth.global.auth.apple.dto.AppleUserInfo; +import leets.weeth.global.auth.apple.exception.AppleAuthenticationException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; @@ -102,7 +103,7 @@ public AppleUserInfo verifyAndDecodeIdToken(String idToken) { ApplePublicKey matchedKey = publicKeys.keys().stream() .filter(key -> key.kid().equals(kid)) .findFirst() - .orElseThrow(() -> new RuntimeException("일치하는 애플 공개키를 찾을 수 없습니다.")); + .orElseThrow(AppleAuthenticationException::new); // 4. 공개키로 ID Token 검증 PublicKey publicKey = generatePublicKey(matchedKey); @@ -128,7 +129,7 @@ public AppleUserInfo verifyAndDecodeIdToken(String idToken) { } catch (Exception e) { log.error("애플 ID Token 검증 실패", e); - throw new RuntimeException("애플 ID Token 검증 실패: " + e.getMessage()); + throw new AppleAuthenticationException(); } } @@ -172,7 +173,7 @@ private String generateClientSecret() { } catch (Exception e) { log.error("애플 Client Secret 생성 실패", e); - throw new RuntimeException("애플 Client Secret 생성 실패: " + e.getMessage()); + throw new AppleAuthenticationException(); } } @@ -206,7 +207,7 @@ private PublicKey generatePublicKey(ApplePublicKey applePublicKey) { return keyFactory.generatePublic(publicKeySpec); } catch (Exception ex) { log.error("애플 공개키 생성 실패", ex); - throw new RuntimeException("애플 공개키 생성 실패: " + ex.getMessage()); + throw new AppleAuthenticationException(); } } @@ -240,7 +241,7 @@ private Map parseJson(String json) { com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); return objectMapper.readValue(json, Map.class); } catch (Exception e) { - throw new RuntimeException("JSON 파싱 실패: " + e.getMessage()); + throw new RuntimeException("JSON 파싱 실패"); } } } diff --git a/src/main/java/leets/weeth/global/auth/apple/exception/AppleAuthenticationException.java b/src/main/java/leets/weeth/global/auth/apple/exception/AppleAuthenticationException.java new file mode 100644 index 00000000..29216523 --- /dev/null +++ b/src/main/java/leets/weeth/global/auth/apple/exception/AppleAuthenticationException.java @@ -0,0 +1,9 @@ +package leets.weeth.global.auth.apple.exception; + +import leets.weeth.global.common.exception.BusinessLogicException; + +public class AppleAuthenticationException extends BusinessLogicException { + public AppleAuthenticationException() { + super(401, "애플 로그인에 실패했습니다."); + } +} From 4c336cd5b4ecdb721eb9e74313981e39b2f38d90 Mon Sep 17 00:00:00 2001 From: hyxklee Date: Wed, 12 Nov 2025 18:53:51 +0900 Subject: [PATCH 114/114] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=98=B8=EC=B6=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weeth/domain/user/application/usecase/UserUseCaseImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java index a3205fc3..5a4ddab7 100644 --- a/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java +++ b/src/main/java/leets/weeth/domain/user/application/usecase/UserUseCaseImpl.java @@ -271,7 +271,6 @@ public SocialLoginResponse appleLogin(Login dto) { @Override @Transactional public void appleRegister(Register dto) { - isDevEnvironment(); validate(dto); // Apple authCode로 토큰 교환 후 ID Token 검증 및 사용자 정보 추출