From 49ad8d77e58b44962c9ebf69ce3ae63e053643f0 Mon Sep 17 00:00:00 2001 From: Jeonghoon Park <39729721+shb03323@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:10:17 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=EB=B0=B1=EC=97=94=EB=93=9C=20=ED=95=A9?= =?UTF-8?q?=EB=B3=91=20=EC=9E=91=EC=97=85=20(#593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 빈 공백 추가 * github pr 자동 할당 기능 설정 (#563) docs: github pr 자동 할당 기능 설정 * 서포터 피드백 작성 유무 반환 추가, 러너 게시글 조회수 flyway 컬럼명 수정, 서포터 지원자 수 조회시 dto 사용하도록 수정 (#555) * feat: 러너 게시글의 서포터 피드백 유무 필드(컬럼) 구현 * test: 러너 게시글에 서포터 피드백 유무 컬럼 추가 후 테스트 변경 * feat: 러너 게시글 응답에 서포터 피드백 유무 정보 추가 * chore: 러너 게시글에 서포터 피드백 유무 flyway 테이블 컬럼 추가 * chore: 러너 게시글 조회수 컬럼명 flyway 수정 추가 * style: IsReviewed get 메서드 네이밍 get으로 통일되게 수정 * fix: IsReviewed 내부 값 반환 수정 * test: 테스트 내부 entityManager 사용 후 close 하도록 수정 * refactor: 러너 게시글 식별자값 목록으로 서포터 지원자 수 조회시 매핑된 서포터 지원자 수 객체를 반환하도록 레포지터리 메서드 변경 * refactor: 매핑된 서포터 지원자 수 객체를 사용하도록 서비스, 컨트롤러 변경 * style: 공백 및 개행 스타일 수정 * style: toString 메서드 삭제 * refactor: dto 클래스를 레코드로 변경 * test: entityManager flush, close 순서 변경 * 이미 서포터 리뷰를 작성했으면 예외 반환하도록 함 (#552) * refactor: 이미 서포터 리뷰를 작성했으면 예외 반환하도록 함 * test: Repository 테스트 추가 * test: 테스트에 em.flush와 em.clear 추가 * style: 예외 메세지 이름 변경 * feat: Feedback 을 하면 isReviewed 속성이 true 가 되도록 변경 * refreshToken 과 accessToken 이 null 일 때 예외 추가 (#564) * feat: refreshToken 과 accessToken 이 null 일 때 예외 추가 * feat: deploy 에 github branch 설정 추가 * test: AssuredSupport post 에서 필요없는 Application json value 삭제 * feat: CookieValue 에 Nullable 추가 * Merbe deb/BE to feat/559 * feat: max threads 100 으로 변경 * deploy 서브 모듈 리프래쉬 토큰 만료 시간 값 변경 (#573) chore: deploy 리프래쉬 토큰 만료 시간 값 변경 * 기본 쓰레드 300 으로 변경 (#583) chore: 쓰레드 300 으로 변경 * 기본 쓰레드(200)으로 변경 및 min spare 100 으로 변경 (#584) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * min spare 1로 변경 (#586) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * refactor min spare 1로 변경 * 톰캣 쓰레드 관련 설정 롤백 (#587) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * refactor min spare 1로 변경 * chore: tomcat 관련 설정 롤백 * 깃허브 소셜 회원 가입시 계정의 닉네임이 없을 경우 기본값으로 저장 (#589) fix: 사용자 이름 null 로 생성시 기본값 '익명의 사용자'를 사용하도록 수정 --------- Co-authored-by: Ethan Co-authored-by: HyunSeo Park (Hyena) --- backend/baton/secret | 2 +- .../baton/domain/member/vo/MemberName.java | 12 +++++------- .../github/response/GithubMemberResponse.java | 3 ++- .../touch/baton/domain/member/MemberTest.java | 16 +++++++++------- .../domain/member/vo/MemberNameTest.java | 19 +++++++++++++++++++ .../baton/domain/member/vo/NameTest.java | 16 ---------------- 6 files changed, 36 insertions(+), 32 deletions(-) create mode 100644 backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/member/vo/NameTest.java diff --git a/backend/baton/secret b/backend/baton/secret index 29cead5ae..a25644c54 160000 --- a/backend/baton/secret +++ b/backend/baton/secret @@ -1 +1 @@ -Subproject commit 29cead5aebf3569086345ada08e57f13094158b8 +Subproject commit a25644c54c052d55ea15f41a4e41221ee34474ed diff --git a/backend/baton/src/main/java/touch/baton/domain/member/vo/MemberName.java b/backend/baton/src/main/java/touch/baton/domain/member/vo/MemberName.java index 2a4dcd958..2c900d3f8 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/vo/MemberName.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/vo/MemberName.java @@ -16,17 +16,15 @@ @Embeddable public class MemberName { + private static final String DEFAULT_VALUE = "익명의 사용자"; + @Column(name = "name", nullable = false) - private String value; + private String value = DEFAULT_VALUE; public MemberName(final String value) { - validateNotNull(value); - this.value = value; - } - - private void validateNotNull(final String value) { if (Objects.isNull(value)) { - throw new IllegalArgumentException("MemberName 객체 내부에 name 은 null 일 수 없습니다."); + return; } + this.value = value; } } diff --git a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java index 05e5001c7..9a3db4030 100644 --- a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java +++ b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java @@ -1,6 +1,7 @@ package touch.baton.infra.auth.oauth.github.response; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.annotation.Nullable; import touch.baton.domain.member.vo.GithubUrl; import touch.baton.domain.member.vo.ImageUrl; import touch.baton.domain.member.vo.MemberName; @@ -13,7 +14,7 @@ @JsonNaming(SnakeCaseStrategy.class) public record GithubMemberResponse(String id, - String name, + @Nullable String name, String login, String htmlUrl, String avatarUrl diff --git a/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java b/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java index a3ff8dbc1..d1fe576e9 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java @@ -37,19 +37,21 @@ void success() { ).doesNotThrowAnyException(); } - @DisplayName("이름에 null 이 들어갈 경우 예외가 발생한다.") + @DisplayName("이름에 null 이 들어갈 경우 기본값으로 생성한다.") @Test - void fail_if_name_is_null() { - assertThatThrownBy(() -> Member.builder() - .memberName(null) + void success_if_name_is_null_then_default_value() { + // given + final Member member = Member.builder() + .memberName(new MemberName(null)) .socialId(new SocialId("testSocialId")) .oauthId(new OauthId("dsigjh98gh230gn2oinv913bcuo23nqovbvu93b12voi3bc31j")) .githubUrl(new GithubUrl("github.com/hyena0608")) .company(new Company("우아한형제들")) .imageUrl(new ImageUrl("imageUrl")) - .build() - ).isInstanceOf(MemberDomainException.class) - .hasMessage("Member 의 name 은 null 일 수 없습니다."); + .build(); + + // when, then + assertThat(member.getMemberName()).isEqualTo(new MemberName("익명의 사용자")); } @DisplayName("socialId에 null 이 들어갈 경우 예외가 발생한다.") diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java new file mode 100644 index 000000000..6cf5fef9c --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java @@ -0,0 +1,19 @@ +package touch.baton.domain.member.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MemberNameTest { + + @DisplayName("value 가 null 이면 기본값으로_생성한다") + @Test + void fail_if_value_is_null_then_default_value() { + // given + final MemberName memberName = new MemberName(null); + + // when, then + assertThat(memberName.getValue()).isEqualTo("익명의 사용자"); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/NameTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/NameTest.java deleted file mode 100644 index 062e90c46..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/member/vo/NameTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package touch.baton.domain.member.vo; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class NameTest { - - @DisplayName("value 가 null 이면 예외가 발생한다.") - @Test - void fail_if_value_is_null() { - assertThatThrownBy(() -> new MemberName(null)) - .isInstanceOf(IllegalArgumentException.class); - } -} From 1638096d7792fdd3b958549f1672e81fbf2c2f45 Mon Sep 17 00:00:00 2001 From: Jeonghoon Park <39729721+shb03323@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:13:26 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=EB=B0=B1=EC=97=94=EB=93=9C=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=9E=91=EC=97=85=20(#657)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 빈 공백 추가 * github pr 자동 할당 기능 설정 (#563) docs: github pr 자동 할당 기능 설정 * 서포터 피드백 작성 유무 반환 추가, 러너 게시글 조회수 flyway 컬럼명 수정, 서포터 지원자 수 조회시 dto 사용하도록 수정 (#555) * feat: 러너 게시글의 서포터 피드백 유무 필드(컬럼) 구현 * test: 러너 게시글에 서포터 피드백 유무 컬럼 추가 후 테스트 변경 * feat: 러너 게시글 응답에 서포터 피드백 유무 정보 추가 * chore: 러너 게시글에 서포터 피드백 유무 flyway 테이블 컬럼 추가 * chore: 러너 게시글 조회수 컬럼명 flyway 수정 추가 * style: IsReviewed get 메서드 네이밍 get으로 통일되게 수정 * fix: IsReviewed 내부 값 반환 수정 * test: 테스트 내부 entityManager 사용 후 close 하도록 수정 * refactor: 러너 게시글 식별자값 목록으로 서포터 지원자 수 조회시 매핑된 서포터 지원자 수 객체를 반환하도록 레포지터리 메서드 변경 * refactor: 매핑된 서포터 지원자 수 객체를 사용하도록 서비스, 컨트롤러 변경 * style: 공백 및 개행 스타일 수정 * style: toString 메서드 삭제 * refactor: dto 클래스를 레코드로 변경 * test: entityManager flush, close 순서 변경 * 이미 서포터 리뷰를 작성했으면 예외 반환하도록 함 (#552) * refactor: 이미 서포터 리뷰를 작성했으면 예외 반환하도록 함 * test: Repository 테스트 추가 * test: 테스트에 em.flush와 em.clear 추가 * style: 예외 메세지 이름 변경 * feat: Feedback 을 하면 isReviewed 속성이 true 가 되도록 변경 * refreshToken 과 accessToken 이 null 일 때 예외 추가 (#564) * feat: refreshToken 과 accessToken 이 null 일 때 예외 추가 * feat: deploy 에 github branch 설정 추가 * test: AssuredSupport post 에서 필요없는 Application json value 삭제 * feat: CookieValue 에 Nullable 추가 * Merbe deb/BE to feat/559 * feat: max threads 100 으로 변경 * deploy 서브 모듈 리프래쉬 토큰 만료 시간 값 변경 (#573) chore: deploy 리프래쉬 토큰 만료 시간 값 변경 * 기본 쓰레드 300 으로 변경 (#583) chore: 쓰레드 300 으로 변경 * 기본 쓰레드(200)으로 변경 및 min spare 100 으로 변경 (#584) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * min spare 1로 변경 (#586) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * refactor min spare 1로 변경 * 톰캣 쓰레드 관련 설정 롤백 (#587) * chore: 쓰레드 300 으로 변경 * refchore: 쓰레드 200 으로 변경 및 min spare 100 으로 변경 * refactor min spare 1로 변경 * chore: tomcat 관련 설정 롤백 * 깃허브 소셜 회원 가입시 계정의 닉네임이 없을 경우 기본값으로 저장 (#589) fix: 사용자 이름 null 로 생성시 기본값 '익명의 사용자'를 사용하도록 수정 * 커서 기반 페이지네이션 (#596) * refactor: 사용하지 않는 상수 제거 * chore: querydsl 추가 * chore: gitignore에 Qclass 숨기기 * chore: build 파일 형태 변경 * feat: QueryDsl config 설정 * feat: querydsl 실험을 위한 임시 컨트롤러 설정 * feat: 커서 기반 페이지네이션 레포지토리 구현 * chore: 서브모듈 업데이트 * refactor: 중복 테스트 제거 * chore: 서브 모듈 적용 * feat: 페이지네이션 쿼리 2개로 개선 * refactor: RunnerPostReadRepository 삭제 * test: 페이지 정보와 리뷰 상태로 게시글 조회 테스트 (두번째 페이지인 경우) * feat: 첫 페이지 조회 기능 구현 * refactor: 필요없는 dto 삭제 * feat: 러너 게시글 지원자 수 저장 객체 생성 * feat: 러너 게시글 페이지 조회 기능 구현 * refactor: 필요 없는 객체 삭제 * refactor: RunnerPost 목록으로 RunnerPostTag 목록 조회하는 기능 RunnerPostTagRepository 로 이동 * refactor: 필요 없는 객체 삭제 * refactor: 태그 이름으로 게시글 조회 기능 구현 * test: 태그 이름으로 조회 기능 테스트 * test: 태그 이름으로 조회 인수 테스트 * test: 태그 이름으로 조회 docs 테스트 수정 * refactor: 충돌 해결 * refactor: 동적 쿼리 적용 * test: runnerPostService 테스트 * refactor: 주석 작성 * chore: 리플랙션 의존성 제거 * chore: 빌드 파일 수정 * chore: 빌드 파일 수정 * 아키텍쳐 개선 (#608) * refactor: Member 패키지 구조 변경 * refactor: Runner 패키지 구조 변경 * refactor: Supporter 패키지 구조 변경 * refactor: Oauth 패키지 구조 변경 * refactor: RunnerPost 패키지 구조 변경 * refactor: Tag 패키지 구조 변경 * refactor: TechnicalTag 패키지 구조 변경 * refactor: Feedback 패키지 구조 변경 * refactor: 패키지 구조 변경 완료 * test: RunnerPostAssured 구조 개선 * test: Member, Feedback 구조 개선 * test: tag 구조 개선 * test: assured 구조 개선 완료 * 최종 페이지네이션 (#611) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * "/profile/runner" api 삭제 (#619) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * refactor: api 삭제 * refactor: supporter read response 삭제 * refactor: runner response 두 개 합치기 * refactor: 충돌 해결 * 무중단 배포 실험 (#623) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * refactor: api 삭제 * refactor: supporter read response 삭제 * refactor: runner response 두 개 합치기 * refactor: 충돌 해결 * refactor: dev cicd 스크립트 변경 * 무중단 배포 스크립트 작성 (#631) * test: 전체 조회 인수 테스트 * test: 지원자 수 조회 repo 테스트 * refactor: page model attribute 적용 * refactor: 서포터 관련 게시글 페이지네이션 * refactor: querydsl repository 추상화 해제 * test: 서포터 id 로 러너 게시글 조회 레포지토리 테스트 * feat: runner id 로 러너 게시글 조회 repository 구현 * refactor: response dto query 패키지로 이동 * refactor: PageResponse 로 응답 변경 * refactor: 필요없는 메소드 삭제 * refactor: runnerpost 테스트 패키징 변경 * test: runner로 runnerPost 조회 api 테스트 * docs: 명세서 오류 수정 * refactor: pageResponse 생성자 인자 변경 * refactor: pathParams 에서 limit + 1 되도록 수정 * refactor: querydsl config 테스트용 생성 * refactor: 테스트 자원 정리 * feat: PageInfo에 다음 커서 추가 * refactor: PageParams 패키지 위치 변경 * refactor: api 삭제 * refactor: supporter read response 삭제 * refactor: runner response 두 개 합치기 * refactor: 충돌 해결 * refactor: dev cicd 스크립트 변경 * feat: CICD 스크립트 수정 * refactor: dev docker compose 삭제 * restdocs 테스트 설정 변경 및 테스트 컨텍스트 로딩 개선 (#633) * test: restdocs 테스트 구성 정보 수정 * test: restdocs 각 테스트 setup 수정 * test: AuthCode 테스트용 클래스명 수정 * test: JwtConfig 테스트용 클래스명 수정 * test: GithubOauthConfig 테스트용 페이크 객체 추가 * test: RestdocsConfig beforeEach 세팅 수정 * test: FakeGithubOauthConfig 삭제 * test: Restdocs 테스트시 로그 print 삭제 * logout시 refreshtoken이 삭제되는 api 구현 (#632) * feat: 로그아웃 API 구현 * refactor: refreshtoken delete api 이름 변경 * refactor: dev에 localhost:3000 cors 추가 * refactor: 리뷰반영 deleteMapping -> patchMapping으로 변경 * test: doPring 제거 * test: 인스턴스 변수 띄어쓰기 추가 * test: repositoryTestConfig에 persistMember 추가 및 리팩토링 * test: given when then 에서 컨벤션 일관화 * zero-downtime-deploy.sh 경로 변경 (#634) * feat: 로그아웃 API 구현 * refactor: refreshtoken delete api 이름 변경 * refactor: dev에 localhost:3000 cors 추가 * refactor: 리뷰반영 deleteMapping -> patchMapping으로 변경 * test: doPring 제거 * test: 인스턴스 변수 띄어쓰기 추가 * test: repositoryTestConfig에 persistMember 추가 및 리팩토링 * test: given when then 에서 컨벤션 일관화 * refactor: zero-downtime-deploy 경로 변경 * 알림 기능 구현 (이벤트 발행 및 구독, 알림 조회, 알림 삭제, 알림 업데이트) (#625) * feat: 알람 내 값객체 구현 (제목, 내용, 연관 식별자값, 읽기 여부, 알람 타입, 알람 메시지 모음) * feat: 알람 엔티티 구현 * feat: 알람 예외 클래스 구현 * feat: 알람 목록 조회 레포지터리 기능 구현 * feat: 알람 소프트 딜리트, 읽기 여부 true 업데이트 레포지터리 기능 구현 * feat: 알람 소프트 딜리트, 읽기 여부 true 업데이트 서비스 기능 구현 * feat: 알람 목록 조회 서비스 기능 구현 * feat: 알람 읽음 여부 기록 및 삭제 API 구현 * feat: 사용자 알람 목록 조회 API 구현 * feat: 러너 게시글 식별자값으로 러너 게시글과 서포터, 사용자 조회 레포지터리 기능 구현 * feat: 알람 이벤트 (러너 게시글 리뷰 완료, 러너 게시글 서포터 할당, 러너 게시글 서포터 지원) 리스너 구현 * feat: 이벤트 (러너 게시글 리뷰 완료, 러너 게시글 서포터 할당, 러너 게시글 서포터 지원) 발행 구현 * feat: 알람 생성 시간 초단위 삭제를 위한 BaseEntity 상속 클래스 구현 * refactor: 알람 이벤트 리스너 내부 메서드 리팩터링 * test: 로그인된 사용자 알람 목록 조회에서 알람 생성시간 테스트 수정 * test: 테스트용 알람 조회 레포지터리 구현 및 테스트용 회원 조회 레포지터리 수정 * test: 알람 읽음 여부 기록 업데이트 인수 테스트 * test: 러너 게시글 지원자 생성 인수 서포트 내부 사용하지 않는 검증문 삭제 * test: 알람 삭제 인수 테스트 * test: 로그인된 사용자 알람 목록 조회 인수 테스트 * chore: flyway 알람 테이블 생성, 알람 사용자(member) 외래키 제약조건 추가 * test: 알람 삭제, 업데이트 restdocs pathVariable 추가 * docs: 로그인된 사용자 알람 목록 조회, 알람 삭제, 알람 읽음 여부 업데이트 api 문서 추가 * style: 사용하지 않는 메서드 삭제 및 정렬 * test: 테스트 로그 삭제 * style: 사용하지 않는 메서드 삭제 * test: 실험용 테스트 코드 삭제 * docs: API 문서 Index 추가 * style: 알람(Alarm)을 알림(Notification)으로 수정 * test: willDoNothing()을 doNothing()으로 수정 * test: 이벤트 발행 카운트 테스트 수정 * refactor: IsRead 를 정적 팩터리 메서드를 이용해서 생성하도록 리팩터링 * feat: 알림 읽음 여부 수정 기능 구현 * feat: 알림 읽음 여부 수정 서비스 리팩터링 * refactor: 알림 이벤트 리스너 내부 NotificationText 를 문자열로 리팩터링 * feat: 알림 목록 조회 querydsl 기능 구현 * refactor: JpaRepository 조회 레포지터리를 Querydsl 레포지터리로 리팩터링 * test: 알림 Restdocs 테스트 수정 * test: 러너 게시글에 서포터 조인 조회 기능 테스트 수정 * 프로덕션 서비스 무중단 배포 (#640) * refactor: cicd 스크립트 수정 * refactor: cicd 스크립트 수정 * flyway 알림 수정 (#644) chore: flyway 알림 테이블 생성 내 콤마 삭제 * 러너 및 서포터의 게시글 개수 조회 api 구현 (#646) * feat: 러너 식별자 값으로 러너 게시글 카운트 쿼리 작성 * refactor: secret 업데이트 * feat: Supporter 와 관련된 게시글 수 조회 repository 구현 * feat: RunnerId 와 ReviewStatus 로 러너 게시글 조회 service 구현 * feat: RunnerId 와 reviewStatus 로 로그인 된 러너의 게시글 개수 조회 api 구현 * test: 로그인한 러너와 관련된 러너 게시글 개수 조회 인수 테스트 * test: 러너와 연관된 러너 게시글 개수 조회 rest docs 테스트 * refactor: SupporterRunnerPostRepository 관련 코드 정리 * test: supporter 테스트 파일 구조 변경 * feat: 서포터 식별자 값으로 러너 게시글 개수 조회 서비스 로직 구현 * feat: 서포터 식별자 값으로 러너 게시글 개수 조회 api 구현 * test: 서포터 관련 러너 게시글 개수 조회 인수 테스트 * test: 로그인한 서포터 관련 러너 게시글 개수 조회 api 테스트 * test: 잘못된 테스트 수정 * test: 서포터가 리뷰 완료한 러너 게시글 개수 조회 api 테스트 * refactor: 서브 모듈 업데이트 * refactor: reference 타입을 primitive 타입으로 변경 * RunnerPost reviewStatus 인덱스 추가 (#650) feat: flyway에 review status enum 추가하는 기능 추가 * 태그 목록 검색 기능 리팩터링 (#637) * refactor: 입력된 문자열로 태그 목록 검색 기능 리팩터링 * refactor: TagQueryService 태그 목록 검색 기능 리팩터링 * chore: 러너 게시글 태그 패키지 이동 * refactor: TagQueryController 태그 목록 검색 기능 리팩터링 * test: 인수 테스트 검증용 TestTagQueryRepository 수정 * refactor: 태그 목록 검색 조건인 TagReducedName 이 null 이거나 공백일 경우 빈 목록을 서비스 계층에서 반환하도록 수정 * Nginx와 Spring 로그가 같은 식별자값을 가지도록 수정 (#654) feat: Nginx의 해쉬값을 받아서 spring에서도 같이 출력하도록 함 --------- Co-authored-by: Ethan Co-authored-by: HyunSeo Park (Hyena) --- .github/workflows/deploy-be-ci-cd-push.yml | 8 +- .github/workflows/dev-be-ci-cd-push.yml | 9 +- backend/baton/.gitignore | 1 + backend/baton/build.gradle | 6 + backend/baton/secret | 2 +- .../src/docs/asciidoc/GithubOauthApi.adoc | 16 +- .../docs/asciidoc/NotificationDeleteApi.adoc | 29 + .../asciidoc/NotificationLoginReadApi.adoc | 29 + .../docs/asciidoc/NotificationUpdateApi.adoc | 29 + .../src/docs/asciidoc/OauthLogoutApi.adoc | 25 + .../src/docs/asciidoc/RunnerPostReadApi.adoc | 92 +- .../src/docs/asciidoc/RunnerReadApi.adoc | 6 +- backend/baton/src/docs/asciidoc/index.adoc | 15 + .../ScheduleRunnerPostRepository.java | 2 +- .../baton/config/ArgumentResolverConfig.java | 6 +- .../touch/baton/config/QuerydslConfig.java | 19 + .../java/touch/baton/config/WebMvcConfig.java | 7 +- .../config/converter/OauthTypeConverter.java | 2 +- .../converter/ReviewStatusConverter.java | 2 +- .../baton/config/filter/FilterConfig.java | 2 +- .../baton/config/filter/MDCLoggingFilter.java | 7 +- .../domain/common/TruncatedBaseEntity.java | 22 + .../common/exception/ClientErrorCode.java | 1 + .../domain/common/request/PageParams.java | 14 + .../domain/common/response/IdExtractable.java | 6 + .../domain/common/response/PageResponse.java | 53 +- .../baton/domain/common/vo/ChattingCount.java | 31 - .../baton/domain/common/vo/TotalRating.java | 27 - .../{ => command}/SupporterFeedback.java | 12 +- .../FeedbackCommandController.java} | 16 +- .../SupporterFeedbackCommandRepository.java | 9 + .../service/FeedbackCommandService.java} | 40 +- .../dto}/SupporterFeedBackCreateRequest.java | 2 +- .../{ => command}/vo/Description.java | 2 +- .../feedback/{ => command}/vo/ReviewType.java | 2 +- .../SupporterFeedbackRepository.java | 9 - .../domain/member/{ => command}/Member.java | 14 +- .../{runner => member/command}/Runner.java | 17 +- .../command}/Supporter.java | 17 +- .../command}/SupporterRunnerPost.java | 8 +- .../controller/MemberBranchController.java | 11 +- .../controller/RunnerCommandController.java | 32 + .../SupporterCommandController.java | 32 + .../response/LoginMemberInfoResponse.java | 4 +- .../repository/MemberCommandRepository.java | 7 + .../SupporterCommandRepository.java | 7 + .../SupporterRunnerPostCommandRepository.java | 13 + .../service}/GithubBranchManageable.java | 2 +- .../service/RunnerCommandService.java} | 45 +- .../service/SupporterCommandService.java | 53 ++ .../service/dto/GithubRepoNameRequest.java | 2 +- .../service/dto/RunnerUpdateRequest.java | 16 +- .../service/dto/SupporterUpdateRequest.java | 7 +- .../member/{ => command}/vo/Company.java | 2 +- .../member/{ => command}/vo/GithubUrl.java | 2 +- .../member/{ => command}/vo/ImageUrl.java | 2 +- .../command}/vo/Introduction.java | 3 +- .../member/{ => command}/vo/MemberName.java | 2 +- .../command}/vo/Message.java | 2 +- .../member/{ => command}/vo/OauthId.java | 2 +- .../command}/vo/ReviewCount.java | 2 +- .../member/{ => command}/vo/SocialId.java | 2 +- .../exception/RunnerBusinessException.java | 2 +- .../exception/RunnerDomainException.java | 2 +- .../exception/RunnerRequestException.java | 2 +- .../exception/SupporterBusinessException.java | 2 +- .../exception/SupporterDomainException.java | 2 +- .../exception/SupporterRequestException.java | 2 +- .../controller/MemberQueryController.java} | 10 +- .../controller/RunnerQueryController.java | 33 + .../controller/SupporterQueryController.java | 35 + .../controller/response/RunnerResponse.java | 56 +- .../response/SupporterResponse.java | 4 +- .../repository/RunnerQueryRepository.java} | 6 +- .../repository/SupporterQueryRepository.java} | 6 +- .../SupporterRunnerPostQueryRepository.java | 39 + .../query/service/RunnerQueryService.java | 21 + .../query/service/SupporterQueryService.java | 21 + .../member/repository/MemberRepository.java | 7 - .../notification/command/Notification.java | 146 +++ .../NotificationCommandController.java | 38 + .../event/NotificationEventListener.java | 82 ++ .../NotificationCommandRepository.java | 7 + .../service/NotificationCommandService.java | 35 + .../notification/command/vo/IsRead.java | 35 + .../command/vo/NotificationMessage.java | 32 + .../command/vo/NotificationReferencedId.java | 30 + .../command/vo/NotificationTitle.java | 32 + .../command/vo/NotificationType.java | 6 + .../NotificationBusinessException.java | 10 + .../NotificationDomainException.java | 10 + .../NotificationRequestException.java | 11 + .../NotificationQueryController.java | 31 + .../response/NotificationResponse.java | 31 + .../response/NotificationResponses.java | 19 + .../NotificationQuerydslRepository.java | 27 + .../service/NotificationQueryService.java | 21 + .../oauth/client/OauthInformationClient.java | 11 - .../{ => command}/AuthorizationHeader.java | 2 +- .../oauth/{ => command}/OauthInformation.java | 14 +- .../domain/oauth/{ => command}/OauthType.java | 2 +- .../authcode/AuthCodeRequestUrlProvider.java | 4 +- .../AuthCodeRequestUrlProviderComposite.java | 6 +- .../client/OauthInformationClient.java | 11 + .../OauthInformationClientComposite.java | 8 +- .../controller/OauthCommandController.java} | 32 +- .../exception/OauthBusinessException.java | 2 +- .../exception/OauthRequestException.java | 2 +- .../OauthMemberCommandRepository.java | 15 + .../OauthRunnerCommandRepository.java} | 8 +- .../OauthSupporterCommandRepository.java} | 8 +- .../RefreshTokenCommandRepository.java | 17 + .../service/OauthCommandService.java} | 80 +- .../{ => command}/token/AccessToken.java | 2 +- .../oauth/{ => command}/token/ExpireDate.java | 2 +- .../{ => command}/token/RefreshToken.java | 6 +- .../{ => command}/token/SocialToken.java | 2 +- .../oauth/{ => command}/token/Token.java | 2 +- .../oauth/{ => command}/token/Tokens.java | 2 +- .../RefreshTokenDomainException.java | 2 +- .../resolver/AuthMemberPrincipal.java | 2 +- .../AuthMemberPrincipalArgumentResolver.java | 28 +- .../resolver/AuthRunnerPrincipal.java | 2 +- .../AuthRunnerPrincipalArgumentResolver.java | 31 +- .../resolver/AuthSupporterPrincipal.java | 2 +- ...uthSupporterPrincipalArgumentResolver.java | 23 +- .../UserPrincipalArgumentResolver.java | 6 +- .../repository/OauthMemberRepository.java | 15 - .../repository/RefreshTokenRepository.java | 15 - .../controller/RunnerProfileController.java | 63 -- .../response/RunnerMyProfileResponse.java | 10 - .../response/RunnerProfileResponse.java | 36 - .../runnerpost/{ => command}/RunnerPost.java | 56 +- .../command/RunnerPostsApplicantCount.java | 46 + .../RunnerPostCommandController.java | 106 +++ .../event/RunnerPostApplySupporterEvent.java | 4 + .../event/RunnerPostAssignSupporterEvent.java | 4 + .../RunnerPostReviewStatusDoneEvent.java | 4 + .../RunnerPostBusinessException.java | 2 +- .../exception/RunnerPostDomainException.java | 2 +- .../exception/RunnerPostRequestException.java | 2 +- .../exception/validator/FutureValidator.java | 2 +- .../validator/MaxLengthValidator.java | 2 +- .../exception/validator/UrlValidator.java | 2 +- .../exception/validator/ValidFuture.java | 2 +- .../exception/validator/ValidMaxLength.java | 2 +- .../exception/validator/ValidNotUrl.java | 2 +- .../RunnerPostCommandRepository.java | 7 + .../dto/RunnerPostApplicantCountDto.java | 4 + .../service/RunnerPostCommandService.java | 177 ++++ .../dto/RunnerPostApplicantCreateRequest.java | 4 +- .../service/dto/RunnerPostCreateRequest.java | 8 +- .../service/dto/RunnerPostUpdateRequest.java | 2 +- .../{ => command}/vo/CuriousContents.java | 2 +- .../runnerpost/{ => command}/vo/Deadline.java | 2 +- .../{ => command}/vo/ImplementedContents.java | 2 +- .../{ => command}/vo/IsReviewed.java | 2 +- .../{ => command}/vo/PostscriptContents.java | 2 +- .../{ => command}/vo/PullRequestUrl.java | 2 +- .../{ => command}/vo/ReviewStatus.java | 6 +- .../command}/vo/Title.java | 2 +- .../command}/vo/WatchedCount.java | 2 +- .../controller/RunnerPostController.java | 255 ------ .../controller/RunnerPostReadController.java | 71 -- .../response/RunnerPostReadResponses.java | 27 - .../SupporterResponseTestVersion.java | 16 - .../controller/RunnerPostQueryController.java | 128 +++ .../response/RunnerPostResponse.java | 119 +-- .../response/SupporterRunnerPostResponse.java | 6 +- .../SupporterRunnerPostResponses.java | 2 +- .../repository/RunnerPostPageRepository.java | 123 +++ .../repository/RunnerPostQueryRepository.java | 57 ++ .../dto/ApplicantCountMappingDto.java | 2 +- .../query/service/RunnerPostQueryService.java | 136 +++ .../repository/RunnerPostReadRepository.java | 54 -- .../repository/RunnerPostRepository.java | 60 -- .../repository/dto/ApplicantCountDto.java | 4 - .../service/RunnerPostReadService.java | 35 - .../runnerpost/service/RunnerPostService.java | 235 ----- .../dto/RunnerPostCreateTestRequest.java | 13 - .../controller/SupporterController.java | 29 - .../SupporterProfileController.java | 50 - .../response/SupporterReadResponses.java | 13 - .../SupporterRunnerPostRepository.java | 59 -- .../supporter/service/SupporterService.java | 67 -- .../tag/{ => command}/RunnerPostTag.java | 4 +- .../tag/{ => command}/RunnerPostTags.java | 2 +- .../baton/domain/tag/{ => command}/Tag.java | 5 +- .../repository/TagCommandRepository.java | 12 + .../tag/{ => command}/vo/TagReducedName.java | 9 +- .../domain/tag/controller/TagController.java | 36 - .../response/TagSearchResponses.java | 12 - .../query/controller/TagQueryController.java | 31 + .../response/TagSearchResponse.java | 4 +- .../response/TagSearchResponses.java | 19 + .../RunnerPostTagQueryRepository.java} | 6 +- .../repository/TagQuerydslRepository.java | 26 + .../tag/query/service/TagQueryService.java | 27 + .../domain/tag/repository/TagRepository.java | 26 - .../baton/domain/tag/service/TagService.java | 24 - .../{ => command}/RunnerTechnicalTag.java | 4 +- .../{ => command}/RunnerTechnicalTags.java | 2 +- .../{ => command}/SupporterTechnicalTag.java | 4 +- .../{ => command}/SupporterTechnicalTags.java | 2 +- .../{ => command}/TechnicalTag.java | 2 +- .../RunnerTechnicalTagCommandRepository.java} | 8 +- ...pporterTechnicalTagCommandRepository.java} | 8 +- .../TechnicalTagQueryRepository.java | 12 + .../repository/TechnicalTagRepository.java | 12 - .../baton/infra/auth/jwt/JwtDecoder.java | 4 +- .../GithubAuthCodeRequestUrlProvider.java | 4 +- .../client/GithubInformationClient.java | 6 +- .../github/response/GithubMemberResponse.java | 14 +- .../infra/github/GithubBranchManager.java | 2 +- .../baton/src/main/resources/application.yml | 1 + ...V20231007_1__create_table_notification.sql | 13 + ...alter_table_notification_constraint_fk.sql | 3 + ...ate_review_status_index_on_runner_post.sql | 2 + .../baton/assure/common/AssuredSupport.java | 19 + .../baton/assure/common/JwtTestManager.java | 2 +- .../assure/common/OauthLoginTestManager.java | 3 +- .../SupporterFeedbackCreateAssuredTest.java | 38 +- ...upporterFeedbackCreateAssuredSupport.java} | 26 +- .../MemberBranchAssuredTest.java} | 13 +- .../command}/RunnerUpdateAssuredTest.java | 75 +- .../command}/SupporterUpdateAssuredTest.java | 32 +- .../MemberQueryAssuredTest.java} | 17 +- .../query/RunnerQueryAssuredTest.java} | 20 +- .../query/SupporterQueryAssuredTest.java} | 23 +- .../MemberBranchCreateAssuredSupport.java} | 31 +- .../command/RunnerUpdateAssuredSupport.java | 96 ++ .../SupporterUpdateAssuredSupport.java | 78 ++ .../query/MemberQueryAssuredSupport.java} | 28 +- .../query/RunnerQueryAssuredSupport.java | 103 +++ .../query/SupporterQueryAssuredSupport.java} | 63 +- .../NotificationDeleteAssuredTest.java | 73 ++ .../NotificationUpdateAssuredTest.java | 73 ++ .../query/NotificationQueryAssuredTest.java | 91 ++ .../command/NotificationDeleteSupport.java | 59 ++ .../command/NotificationUpdateSupport.java | 59 ++ .../query/NotificationQuerySupport.java | 60 ++ .../assure/oauth/OauthAssuredSupport.java | 32 +- .../assure/oauth/OauthCreateAssuredTest.java | 6 +- .../assure/oauth/OauthDeleteAssuredTest.java | 67 ++ .../oauth/OauthRefreshTokenAssuredTest.java | 26 +- .../repository/TestMemberQueryRepository.java | 33 + .../repository/TestMemberRepository.java | 17 - .../TestNotificationCommandRepository.java | 6 + .../TestRefreshTokenRepository.java | 6 +- .../TestRunnerPostQueryRepository.java | 27 + .../TestRunnerPostReadRepository.java | 18 - .../repository/TestRunnerPostRepository.java | 13 - ...ry.java => TestRunnerQueryRepository.java} | 8 +- ...java => TestSupporterQueryRepository.java} | 8 +- ...estSupporterRunnerPostQueryRepository.java | 12 + .../TestSupporterRunnerPostRepository.java | 10 - .../repository/TestTagQuerydslRepository.java | 13 + .../assure/repository/TestTagRepository.java | 6 - .../TestTechnicalTagQueryRepository.java | 6 + .../TestTechnicalTagRepository.java | 6 - .../assure/runner/RunnerAssuredSupport.java | 165 ---- .../RunnerReadByRunnerIdAssuredTest.java | 46 - .../runnerpost/RunnerPostAssuredSupport.java | 436 --------- .../runnerpost/RunnerPostReadAssuredTest.java | 125 --- ...stReadWithLoginedSupporterAssuredTest.java | 185 ---- ...SupporterIdAndReviewStatusAssuredTest.java | 113 --- .../RunnerPostCreateAssuredTest.java | 47 +- .../RunnerPostDeleteAssuredTest.java | 53 +- .../RunnerPostUpdateAssuredTest.java | 38 +- ...RunnerPostApplicantCreateAssuredTest.java} | 37 +- ...RunnerPostApplicantDeleteAssuredTest.java} | 25 +- .../RunnerPostApplicantAssuredTest.java} | 35 +- .../RunnerPostCountByRunnerAssuredTest.java | 60 ++ ...RunnerPostCountBySupporterAssuredTest.java | 188 ++++ ...nerPostInRunnerPostDetailAssuredTest.java} | 28 +- .../query/page/RunnerPostPageAssuredTest.java | 262 ++++++ .../RunnerPostPageRunnerAssuredTest.java | 116 +++ .../RunnerPostPageSupporterAssuredTest.java | 253 ++++++ .../support/RunnerPostPageSupport.java | 179 ++++ .../command/RunnerPostCreateSupport.java} | 72 +- .../command/RunnerPostDeleteSupport.java | 60 ++ .../command/RunnerPostUpdateSupport.java | 81 ++ .../RunnerPostApplicantCreateSupport.java | 78 ++ ...nerPostApplicantDeleteAssuredSupport.java} | 6 +- .../RunnerPostApplicantQuerySupport.java | 117 +++ .../RunnerPostCountByRunnerSupport.java | 68 ++ .../RunnerPostCountBySupporterSupport.java | 78 ++ .../query/detail/RunnerPostDetailSupport.java | 117 +++ .../runner/RunnerPostPageRunnerSupport.java | 118 +++ .../RunnerPostPageSupporterSupport.java | 146 +++ .../baton/assure/tag/TagAssuredSupport.java | 85 -- .../baton/assure/tag/TagReadAssuredTest.java | 58 -- .../assure/tag/query/TagReadAssuredTest.java | 59 ++ .../tag/support/query/TagQuerySupport.java | 58 ++ .../RunnerPostDeadlineCheckSchedulerTest.java | 10 +- ...cheduleRunnerPostQueryRepositoryTest.java} | 18 +- .../touch/baton/config/AssuredTestConfig.java | 33 +- .../config/QueryDslRepositoryTestConfig.java | 37 + .../baton/config/RepositoryTestConfig.java | 82 +- .../touch/baton/config/RestdocsConfig.java | 127 ++- .../touch/baton/config/ServiceTestConfig.java | 76 +- .../config/converter/ConverterConfigTest.java | 6 +- ...onfig.java => MockBeanAuthTestConfig.java} | 10 +- ...{MockJwtConfig.java => FakeJwtConfig.java} | 8 +- .../auth/oauth/MockRefreshTokenConfig.java | 2 +- ...{MockAuthCodes.java => FakeAuthCodes.java} | 2 +- ...CodeRequestUrlProviderCompositeConfig.java | 4 +- ...AuthInformationClientCompositeConfig.java} | 24 +- .../document/github/GithubBranchApiTest.java | 32 +- .../delete/NotificationDeleteApiTest.java | 57 ++ ...ificationReadWithLoginedMemberApiTest.java | 83 ++ .../update/NotificationUpdateApiTest.java | 57 ++ .../document/oauth/OauthLogoutApiTest.java | 39 + .../oauth/github/GithubOauthApiTest.java | 57 +- .../oauth/token/RefreshTokenApiTest.java | 35 +- .../MemberReadWithLoginedMemberApiTest.java | 18 +- .../runner/read/RunnerReadByGuestApiTest.java | 33 +- ...=> RunnerReadSimpleByRunnerIdApiTest.java} | 29 +- .../RunnerReadWithLoginedRunnerApiTest.java | 27 +- .../runner/update/RunnerUpdateApiTest.java | 33 +- .../read/SupporterReadByGuestApiTest.java | 30 +- .../update/SupporterUpdateApiTest.java | 28 +- .../create/RunnerPostApplicantApiTest.java | 43 +- .../create/RunnerPostCreateApiTest.java | 32 +- .../delete/RunnerPostDeleteApiTest.java | 23 +- ...nerPostCountOfSupporterByGuestApiTest.java | 72 ++ ...nnerPostCountWithLoginedRunnerApiTest.java | 80 ++ ...rPostCountWithLoginedSupporterApiTest.java | 84 ++ .../read/RunnerPostReadAllApiTest.java | 183 ---- ...nnerPostReadOfSupporterByGuestApiTest.java | 108 +-- .../read/RunnerPostReadOneApiTest.java | 36 +- .../read/RunnerPostReadSearchApiTest.java | 82 +- ...unnerPostReadWithLoginedRunnerApiTest.java | 115 +++ ...erPostReadWithLoginedSupporterApiTest.java | 113 +-- ...PostUpdateApplicantCancelationApiTest.java | 31 +- .../read/SupporterRunnerPostReadApiTest.java | 37 +- .../runnerpost/read/TagReadApiTest.java | 21 +- .../update/RunnerPostUpdateApiTest.java | 37 +- .../domain/common/vo/DescriptionTest.java | 2 +- .../baton/domain/common/vo/TitleTest.java | 1 + .../domain/common/vo/WatchedCountTest.java | 1 + ...pporterFeedbackCommandRepositoryTest.java} | 19 +- ...t.java => FeedbackCommandServiceTest.java} | 44 +- .../touch/baton/domain/member/MemberTest.java | 17 +- .../baton/domain/member/vo/CompanyTest.java | 1 + .../baton/domain/member/vo/GithubUrlTest.java | 1 + .../baton/domain/member/vo/ImageUrlTest.java | 1 + .../domain/member/vo/MemberNameTest.java | 1 + .../baton/domain/member/vo/OauthIdTest.java | 1 + .../baton/domain/member/vo/SocialIdTest.java | 1 + .../command/NotificationTest.java | 207 +++++ .../event/NotificationEventListenerTest.java | 148 +++ .../NotificationCommandRepositoryTest.java | 39 + .../NotificationCommandServiceTest.java | 99 ++ .../notification/command/vo/IsReadTest.java | 44 + .../command/vo/NotificationMessageTest.java | 16 + .../vo/NotificationReferencedIdTest.java | 16 + .../command/vo/NotificationTitleTest.java | 16 + .../NotificationQuerydslRepositoryTest.java | 71 ++ .../service/NotificationQueryServiceTest.java | 77 ++ .../controller/OauthTypeConverterTest.java | 4 +- .../RefreshTokenCommandRepositoryTest.java} | 54 +- .../OauthCommandServiceDeleteTest.java | 72 ++ .../OauthCommandServiceUpdateTest.java} | 83 +- .../{ => command}/token/RefreshTokenTest.java | 24 +- .../oauth/vo/AuthorizationHeaderTest.java | 6 +- .../touch/baton/domain/runner/RunnerTest.java | 19 +- ...st.java => RunnerQueryRepositoryTest.java} | 33 +- ...est.java => RunnerCommandServiceTest.java} | 27 +- ...dTest.java => RunnerQueryServiceTest.java} | 23 +- .../domain/runnerpost/RunnerPostTest.java | 65 +- .../RunnerPostsApplicantCountTest.java | 74 ++ .../exception/validator/UrlValidatorTest.java | 2 +- .../RunnerPostRepositoryDeleteTest.java | 61 +- .../RunnerPostCommandServiceCreateTest.java} | 94 +- .../RunnerPostCommandServiceDeleteTest.java | 119 +++ .../RunnerPostCommandServiceEventTest.java | 119 +++ .../RunnerPostCommandServiceUpdateTest.java} | 106 +-- ...UpdateApplicantCancelationServiceTest.java | 70 +- .../{ => command}/vo/CuriousContentsTest.java | 2 +- .../{ => command}/vo/DeadlineTest.java | 2 +- .../vo/ImplementedContentsTest.java | 2 +- .../{ => command}/vo/IsReviewedTest.java | 2 +- .../vo/PostscriptContentsTest.java | 2 +- .../{ => command}/vo/PullRequestUrlTest.java | 2 +- .../RunnerPostPageRepositoryTest.java | 501 ++++++++++ .../RunnerPostQueryRepositoryReadTest.java | 117 +++ .../RunnerPostQueryRepositoryTest.java | 197 ++++ .../service/RunnerPostQueryServiceTest.java | 859 ++++++++++++++++++ .../RunnerPostReadRepositoryTest.java | 231 ----- .../RunnerPostRepositoryReadTest.java | 92 -- .../repository/RunnerPostRepositoryTest.java | 162 ---- ...SupporterRunnerPostRepositoryReadTest.java | 105 --- .../read/RunnerPostRepositoryReadTest.java | 183 ---- .../service/RunnerPostReadServiceTest.java | 154 ---- .../service/RunnerPostServiceDeleteTest.java | 119 --- .../service/RunnerPostServiceReadTest.java | 360 -------- .../{ => command}/SupporterFeedbackTest.java | 19 +- .../{ => command}/SupporterTest.java | 33 +- .../SupporterQueryRepositoryTest.java} | 22 +- ...porterRunnerPostCommandRepositoryTest.java | 79 ++ .../service/SupporterCommandServiceTest.java | 40 + ...upporterRunnerPostQueryRepositoryTest.java | 112 +++ .../service/SupporterQueryServiceTest.java | 45 + .../SupporterRunnerPostRepositoryTest.java | 209 ----- .../service/SupporterServiceTest.java | 61 -- .../tag/{ => command}/RunnerPostTagTest.java | 44 +- .../tag/{ => command}/RunnerPostTagsTest.java | 22 +- .../domain/tag/{ => command}/TagTest.java | 5 +- .../{ => command}/vo/TagReducedNameTest.java | 2 +- .../RunnerPostTagQueryRepositoryTest.java | 57 ++ .../repository/TagQuerydslRepositoryTest.java | 103 +++ .../query/service/TagQueryServiceTest.java | 77 ++ .../RunnerPostTagRepositoryTest.java | 160 ---- .../tag/repository/TagRepositoryReadTest.java | 116 --- .../tag/service/TagServiceReadTest.java | 51 -- .../SupporterTechnicalTagTest.java | 8 +- .../SupporterTechnicalTagsTest.java | 10 +- .../domain/technicaltag/TechnicalTagTest.java | 1 + ...orterTechnicalTagQueryRepositoryTest.java} | 17 +- ...a => TechnicalTagQueryRepositoryTest.java} | 15 +- .../baton/fixture/domain/MemberFixture.java | 14 +- .../fixture/domain/NotificationFixture.java | 27 + .../fixture/domain/RefreshTokenFixture.java | 8 +- .../baton/fixture/domain/RunnerFixture.java | 12 +- .../fixture/domain/RunnerPostFixture.java | 69 +- .../fixture/domain/RunnerPostTagFixture.java | 6 +- .../fixture/domain/RunnerPostTagsFixture.java | 4 +- .../domain/RunnerTechnicalTagsFixture.java | 8 +- .../domain/SupporterFeedbackFixture.java | 12 +- .../fixture/domain/SupporterFixture.java | 12 +- .../domain/SupporterRunnerPostFixture.java | 8 +- .../domain/SupporterTechnicalTagFixture.java | 6 +- .../domain/SupporterTechnicalTagsFixture.java | 4 +- .../baton/fixture/domain/TagFixture.java | 4 +- .../fixture/domain/TechnicalTagFixture.java | 2 +- .../vo/AuthorizationHeaderFixture.java | 2 +- .../baton/fixture/vo/CompanyFixture.java | 2 +- .../fixture/vo/CuriousContentsFixture.java | 2 +- .../baton/fixture/vo/DeadlineFixture.java | 2 +- .../baton/fixture/vo/DescriptionFixture.java | 2 +- .../baton/fixture/vo/ExpireDateFixture.java | 2 +- .../baton/fixture/vo/GithubUrlFixture.java | 2 +- .../baton/fixture/vo/ImageUrlFixture.java | 2 +- .../vo/ImplementedContentsFixture.java | 2 +- .../baton/fixture/vo/IntroductionFixture.java | 2 +- .../baton/fixture/vo/MemberNameFixture.java | 2 +- .../baton/fixture/vo/MessageFixture.java | 2 +- .../vo/NotificationMessageFixture.java | 13 + .../vo/NotificationReferencedIdFixture.java | 13 + .../fixture/vo/NotificationTitleFixture.java | 13 + .../baton/fixture/vo/OauthIdFixture.java | 2 +- .../fixture/vo/PostscriptContentsFixture.java | 2 +- .../fixture/vo/PullRequestUrlFixture.java | 2 +- .../baton/fixture/vo/ReviewCountFixture.java | 2 +- .../baton/fixture/vo/SocialIdFixture.java | 2 +- .../touch/baton/fixture/vo/TitleFixture.java | 2 +- .../touch/baton/fixture/vo/TokenFixture.java | 2 +- .../baton/fixture/vo/WatchedCountFixture.java | 2 +- .../auth/jwt/JwtEncoderAndDecoderTest.java | 2 +- 460 files changed, 11149 insertions(+), 7055 deletions(-) create mode 100644 backend/baton/src/docs/asciidoc/NotificationDeleteApi.adoc create mode 100644 backend/baton/src/docs/asciidoc/NotificationLoginReadApi.adoc create mode 100644 backend/baton/src/docs/asciidoc/NotificationUpdateApi.adoc create mode 100644 backend/baton/src/docs/asciidoc/OauthLogoutApi.adoc create mode 100644 backend/baton/src/main/java/touch/baton/config/QuerydslConfig.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/common/TruncatedBaseEntity.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/common/request/PageParams.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/common/response/IdExtractable.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/common/vo/ChattingCount.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/common/vo/TotalRating.java rename backend/baton/src/main/java/touch/baton/domain/feedback/{ => command}/SupporterFeedback.java (91%) rename backend/baton/src/main/java/touch/baton/domain/feedback/{controller/FeedbackController.java => command/controller/FeedbackCommandController.java} (65%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/feedback/command/repository/SupporterFeedbackCommandRepository.java rename backend/baton/src/main/java/touch/baton/domain/feedback/{service/FeedbackService.java => command/service/FeedbackCommandService.java} (53%) rename backend/baton/src/main/java/touch/baton/domain/feedback/{service => command/service/dto}/SupporterFeedBackCreateRequest.java (93%) rename backend/baton/src/main/java/touch/baton/domain/feedback/{ => command}/vo/Description.java (94%) rename backend/baton/src/main/java/touch/baton/domain/feedback/{ => command}/vo/ReviewType.java (51%) delete mode 100644 backend/baton/src/main/java/touch/baton/domain/feedback/repository/SupporterFeedbackRepository.java rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/Member.java (91%) rename backend/baton/src/main/java/touch/baton/domain/{runner => member/command}/Runner.java (86%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member/command}/Supporter.java (89%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member/command}/SupporterRunnerPost.java (92%) rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/controller/MemberBranchController.java (73%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/command/controller/RunnerCommandController.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/command/controller/SupporterCommandController.java rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/controller/response/LoginMemberInfoResponse.java (70%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/command/repository/MemberCommandRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterCommandRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterRunnerPostCommandRepository.java rename backend/baton/src/main/java/touch/baton/domain/member/{service/dto => command/service}/GithubBranchManageable.java (70%) rename backend/baton/src/main/java/touch/baton/domain/{runner/service/RunnerService.java => member/command/service/RunnerCommandService.java} (57%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/command/service/SupporterCommandService.java rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/service/dto/GithubRepoNameRequest.java (82%) rename backend/baton/src/main/java/touch/baton/domain/{runner => member/command}/service/dto/RunnerUpdateRequest.java (52%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member/command}/service/dto/SupporterUpdateRequest.java (68%) rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/vo/Company.java (94%) rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/vo/GithubUrl.java (94%) rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/vo/ImageUrl.java (94%) rename backend/baton/src/main/java/touch/baton/domain/{common => member/command}/vo/Introduction.java (90%) rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/vo/MemberName.java (93%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member/command}/vo/Message.java (94%) rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/vo/OauthId.java (94%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member/command}/vo/ReviewCount.java (92%) rename backend/baton/src/main/java/touch/baton/domain/member/{ => command}/vo/SocialId.java (94%) rename backend/baton/src/main/java/touch/baton/domain/{runner => member}/exception/RunnerBusinessException.java (83%) rename backend/baton/src/main/java/touch/baton/domain/{runner => member}/exception/RunnerDomainException.java (82%) rename backend/baton/src/main/java/touch/baton/domain/{runner => member}/exception/RunnerRequestException.java (87%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member}/exception/SupporterBusinessException.java (82%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member}/exception/SupporterDomainException.java (82%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member}/exception/SupporterRequestException.java (86%) rename backend/baton/src/main/java/touch/baton/domain/member/{controller/MemberProfileController.java => query/controller/MemberQueryController.java} (64%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/controller/RunnerQueryController.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/controller/SupporterQueryController.java rename backend/baton/src/main/java/touch/baton/domain/{runner => member/query}/controller/response/RunnerResponse.java (55%) rename backend/baton/src/main/java/touch/baton/domain/{supporter => member/query}/controller/response/SupporterResponse.java (96%) rename backend/baton/src/main/java/touch/baton/domain/{runner/repository/RunnerRepository.java => member/query/repository/RunnerQueryRepository.java} (71%) rename backend/baton/src/main/java/touch/baton/domain/{supporter/repository/SupporterRepository.java => member/query/repository/SupporterQueryRepository.java} (71%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/repository/SupporterRunnerPostQueryRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/service/RunnerQueryService.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/service/SupporterQueryService.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/member/repository/MemberRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/Notification.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/controller/NotificationCommandController.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/event/NotificationEventListener.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/repository/NotificationCommandRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/service/NotificationCommandService.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/vo/IsRead.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationMessage.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationReferencedId.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationTitle.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationType.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationBusinessException.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationDomainException.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationRequestException.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/query/controller/NotificationQueryController.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponse.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponses.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/notification/query/service/NotificationQueryService.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/client/OauthInformationClient.java rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/AuthorizationHeader.java (93%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/OauthInformation.java (73%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/OauthType.java (82%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/authcode/AuthCodeRequestUrlProvider.java (51%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/authcode/AuthCodeRequestUrlProviderComposite.java (87%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/command/client/OauthInformationClient.java rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/client/OauthInformationClientComposite.java (82%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{controller/OauthController.java => command/controller/OauthCommandController.java} (75%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/exception/OauthBusinessException.java (80%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/exception/OauthRequestException.java (85%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthMemberCommandRepository.java rename backend/baton/src/main/java/touch/baton/domain/oauth/{repository/OauthRunnerRepository.java => command/repository/OauthRunnerCommandRepository.java} (66%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{repository/OauthSupporterRepository.java => command/repository/OauthSupporterCommandRepository.java} (65%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java rename backend/baton/src/main/java/touch/baton/domain/oauth/{service/OauthService.java => command/service/OauthCommandService.java} (66%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/token/AccessToken.java (82%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/token/ExpireDate.java (95%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/token/RefreshToken.java (92%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/token/SocialToken.java (82%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/token/Token.java (94%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/token/Tokens.java (66%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => command}/token/exception/RefreshTokenDomainException.java (79%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => query}/controller/resolver/AuthMemberPrincipal.java (83%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => query}/controller/resolver/AuthMemberPrincipalArgumentResolver.java (61%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => query}/controller/resolver/AuthRunnerPrincipal.java (83%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => query}/controller/resolver/AuthRunnerPrincipalArgumentResolver.java (61%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => query}/controller/resolver/AuthSupporterPrincipal.java (83%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => query}/controller/resolver/AuthSupporterPrincipalArgumentResolver.java (61%) rename backend/baton/src/main/java/touch/baton/domain/oauth/{ => query}/controller/resolver/UserPrincipalArgumentResolver.java (92%) delete mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthMemberRepository.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/repository/RefreshTokenRepository.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runner/controller/RunnerProfileController.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerMyProfileResponse.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerProfileResponse.java rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/RunnerPost.java (86%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPostsApplicantCount.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/controller/RunnerPostCommandController.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostApplySupporterEvent.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostAssignSupporterEvent.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostReviewStatusDoneEvent.java rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/RunnerPostBusinessException.java (80%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/RunnerPostDomainException.java (79%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/RunnerPostRequestException.java (84%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/validator/FutureValidator.java (92%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/validator/MaxLengthValidator.java (92%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/validator/UrlValidator.java (94%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/validator/ValidFuture.java (92%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/validator/ValidMaxLength.java (92%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/exception/validator/ValidNotUrl.java (91%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/RunnerPostCommandRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/dto/RunnerPostApplicantCountDto.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/service/dto/RunnerPostApplicantCreateRequest.java (65%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/service/dto/RunnerPostCreateRequest.java (85%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/service/dto/RunnerPostUpdateRequest.java (85%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/vo/CuriousContents.java (94%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/vo/Deadline.java (95%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/vo/ImplementedContents.java (94%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/vo/IsReviewed.java (93%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/vo/PostscriptContents.java (94%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/vo/PullRequestUrl.java (94%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => command}/vo/ReviewStatus.java (82%) rename backend/baton/src/main/java/touch/baton/domain/{common => runnerpost/command}/vo/Title.java (93%) rename backend/baton/src/main/java/touch/baton/domain/{common => runnerpost/command}/vo/WatchedCount.java (94%) delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostReadController.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostReadResponses.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterResponseTestVersion.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/RunnerPostQueryController.java rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => query}/controller/response/RunnerPostResponse.java (53%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => query}/controller/response/SupporterRunnerPostResponse.java (88%) rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => query}/controller/response/SupporterRunnerPostResponses.java (81%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java rename backend/baton/src/main/java/touch/baton/domain/runnerpost/{ => query}/repository/dto/ApplicantCountMappingDto.java (79%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepository.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostRepository.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/dto/ApplicantCountDto.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostReadService.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostCreateTestRequest.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterController.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/supporter/controller/response/SupporterReadResponses.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java rename backend/baton/src/main/java/touch/baton/domain/tag/{ => command}/RunnerPostTag.java (95%) rename backend/baton/src/main/java/touch/baton/domain/tag/{ => command}/RunnerPostTags.java (97%) rename backend/baton/src/main/java/touch/baton/domain/tag/{ => command}/Tag.java (93%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/command/repository/TagCommandRepository.java rename backend/baton/src/main/java/touch/baton/domain/tag/{ => command}/vo/TagReducedName.java (83%) delete mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/controller/TagController.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/controller/response/TagSearchResponses.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/query/controller/TagQueryController.java rename backend/baton/src/main/java/touch/baton/domain/tag/{ => query}/controller/response/TagSearchResponse.java (71%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/query/controller/response/TagSearchResponses.java rename backend/baton/src/main/java/touch/baton/domain/tag/{repository/RunnerPostTagRepository.java => query/repository/RunnerPostTagQueryRepository.java} (71%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/query/repository/TagQuerydslRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/query/service/TagQueryService.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/repository/TagRepository.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/tag/service/TagService.java rename backend/baton/src/main/java/touch/baton/domain/technicaltag/{ => command}/RunnerTechnicalTag.java (95%) rename backend/baton/src/main/java/touch/baton/domain/technicaltag/{ => command}/RunnerTechnicalTags.java (94%) rename backend/baton/src/main/java/touch/baton/domain/technicaltag/{ => command}/SupporterTechnicalTag.java (95%) rename backend/baton/src/main/java/touch/baton/domain/technicaltag/{ => command}/SupporterTechnicalTags.java (94%) rename backend/baton/src/main/java/touch/baton/domain/technicaltag/{ => command}/TechnicalTag.java (95%) rename backend/baton/src/main/java/touch/baton/domain/technicaltag/{repository/RunnerTechnicalTagRepository.java => command/repository/RunnerTechnicalTagCommandRepository.java} (61%) rename backend/baton/src/main/java/touch/baton/domain/technicaltag/{repository/SupporterTechnicalTagRepository.java => command/repository/SupporterTechnicalTagCommandRepository.java} (61%) create mode 100644 backend/baton/src/main/java/touch/baton/domain/technicaltag/query/repository/TechnicalTagQueryRepository.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java create mode 100644 backend/baton/src/main/resources/db/migration/V20231007_1__create_table_notification.sql create mode 100644 backend/baton/src/main/resources/db/migration/V20231007_2__alter_table_notification_constraint_fk.sql create mode 100644 backend/baton/src/main/resources/db/migration/V20231011__create_review_status_index_on_runner_post.sql rename backend/baton/src/test/java/touch/baton/assure/feedback/{ => command}/SupporterFeedbackCreateAssuredTest.java (79%) rename backend/baton/src/test/java/touch/baton/assure/feedback/{SupporterFeedbackAssuredSupport.java => support/command/SupporterFeedbackCreateAssuredSupport.java} (66%) rename backend/baton/src/test/java/touch/baton/assure/member/{MemberBranchCreateAssuredTest.java => command/MemberBranchAssuredTest.java} (70%) rename backend/baton/src/test/java/touch/baton/assure/{runner => member/command}/RunnerUpdateAssuredTest.java (54%) rename backend/baton/src/test/java/touch/baton/assure/{supporter => member/command}/SupporterUpdateAssuredTest.java (82%) rename backend/baton/src/test/java/touch/baton/assure/member/{MemberReadWithLoginedMemberAssuredTest.java => query/MemberQueryAssuredTest.java} (63%) rename backend/baton/src/test/java/touch/baton/assure/{runner/RunnerReadWithLoginedRunnerAssuredTest.java => member/query/RunnerQueryAssuredTest.java} (61%) rename backend/baton/src/test/java/touch/baton/assure/{supporter/SupporterReadBySupporterIdAssuredTest.java => member/query/SupporterQueryAssuredTest.java} (70%) rename backend/baton/src/test/java/touch/baton/assure/member/{MemberBranchAssuredSupport.java => support/command/MemberBranchCreateAssuredSupport.java} (52%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/member/support/command/RunnerUpdateAssuredSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/member/support/command/SupporterUpdateAssuredSupport.java rename backend/baton/src/test/java/touch/baton/assure/member/{MemberAssuredSupport.java => support/query/MemberQueryAssuredSupport.java} (60%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/member/support/query/RunnerQueryAssuredSupport.java rename backend/baton/src/test/java/touch/baton/assure/{supporter/SupporterAssuredSupport.java => member/support/query/SupporterQueryAssuredSupport.java} (59%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationDeleteAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationUpdateAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/notification/query/NotificationQueryAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationDeleteSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationUpdateSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/notification/support/query/NotificationQuerySupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/oauth/OauthDeleteAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestMemberQueryRepository.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestMemberRepository.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestNotificationCommandRepository.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostQueryRepository.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostReadRepository.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostRepository.java rename backend/baton/src/test/java/touch/baton/assure/repository/{TestRunnerRepository.java => TestRunnerQueryRepository.java} (73%) rename backend/baton/src/test/java/touch/baton/assure/repository/{TestSupporterRepository.java => TestSupporterQueryRepository.java} (72%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostQueryRepository.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostRepository.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestTagQuerydslRepository.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestTagRepository.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagQueryRepository.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagRepository.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/runner/RunnerAssuredSupport.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/runner/RunnerReadByRunnerIdAssuredTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadAssuredTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedSupporterAssuredTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithSupporterIdAndReviewStatusAssuredTest.java rename backend/baton/src/test/java/touch/baton/assure/runnerpost/{ => command}/RunnerPostCreateAssuredTest.java (91%) rename backend/baton/src/test/java/touch/baton/assure/runnerpost/{ => command}/RunnerPostDeleteAssuredTest.java (85%) rename backend/baton/src/test/java/touch/baton/assure/runnerpost/{ => command}/RunnerPostUpdateAssuredTest.java (82%) rename backend/baton/src/test/java/touch/baton/assure/runnerpost/{RunnerPostAssuredCreateTest.java => command/applicant/RunnerPostApplicantCreateAssuredTest.java} (75%) rename backend/baton/src/test/java/touch/baton/assure/{supporter/SupporterRunnerPostDeleteAssuredTest.java => runnerpost/command/applicant/RunnerPostApplicantDeleteAssuredTest.java} (77%) rename backend/baton/src/test/java/touch/baton/assure/runnerpost/{RunnerPostReadWithLoginedAssuredTest.java => query/applicant/RunnerPostApplicantAssuredTest.java} (71%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/runner/RunnerPostCountByRunnerAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/supporter/RunnerPostCountBySupporterAssuredTest.java rename backend/baton/src/test/java/touch/baton/assure/runnerpost/{RunnerPostReadByRunnerPostIdAssuredTest.java => query/detail/RunnerPostInRunnerPostDetailAssuredTest.java} (81%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/runner/RunnerPostPageRunnerAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/supporter/RunnerPostPageSupporterAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/RunnerPostPageSupport.java rename backend/baton/src/test/java/touch/baton/assure/runnerpost/{RunnerPostAssuredCreateSupport.java => support/command/RunnerPostCreateSupport.java} (53%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostDeleteSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostUpdateSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/applicant/RunnerPostApplicantCreateSupport.java rename backend/baton/src/test/java/touch/baton/assure/{supporter/SupporterRunnerPostAssuredSupport.java => runnerpost/support/command/applicant/RunnerPostApplicantDeleteAssuredSupport.java} (92%) create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/applicant/RunnerPostApplicantQuerySupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/runner/RunnerPostCountByRunnerSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/supporter/RunnerPostCountBySupporterSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/detail/RunnerPostDetailSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/runner/RunnerPostPageRunnerSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/supporter/RunnerPostPageSupporterSupport.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/tag/TagAssuredSupport.java delete mode 100644 backend/baton/src/test/java/touch/baton/assure/tag/TagReadAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/tag/query/TagReadAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/tag/support/query/TagQuerySupport.java rename backend/baton/src/test/java/touch/baton/common/schedule/{ScheduleRunnerPostRepositoryTest.java => ScheduleRunnerPostQueryRepositoryTest.java} (88%) create mode 100644 backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java rename backend/baton/src/test/java/touch/baton/config/infra/auth/{MockAuthTestConfig.java => MockBeanAuthTestConfig.java} (59%) rename backend/baton/src/test/java/touch/baton/config/infra/auth/jwt/{MockJwtConfig.java => FakeJwtConfig.java} (84%) rename backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/{MockAuthCodes.java => FakeAuthCodes.java} (91%) rename backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/client/{MockOauthInformationClientCompositeConfig.java => MockAuthInformationClientCompositeConfig.java} (66%) create mode 100644 backend/baton/src/test/java/touch/baton/document/notification/delete/NotificationDeleteApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/document/notification/read/NotificationReadWithLoginedMemberApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/document/notification/update/NotificationUpdateApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/document/oauth/OauthLogoutApiTest.java rename backend/baton/src/test/java/touch/baton/document/profile/runner/read/{RunnerReadByRunnerIdApiTest.java => RunnerReadSimpleByRunnerIdApiTest.java} (75%) create mode 100644 backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountOfSupporterByGuestApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedRunnerApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedSupporterApiTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java rename backend/baton/src/test/java/touch/baton/domain/feedback/repository/{SupporterFeedbackRepositoryTest.java => SupporterFeedbackCommandRepositoryTest.java} (71%) rename backend/baton/src/test/java/touch/baton/domain/feedback/service/{FeedbackServiceTest.java => FeedbackCommandServiceTest.java} (55%) create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/NotificationTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/event/NotificationEventListenerTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/repository/NotificationCommandRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/service/NotificationCommandServiceTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/vo/IsReadTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationMessageTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationReferencedIdTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationTitleTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/notification/query/service/NotificationQueryServiceTest.java rename backend/baton/src/test/java/touch/baton/domain/oauth/{ => command}/controller/OauthTypeConverterTest.java (88%) rename backend/baton/src/test/java/touch/baton/domain/oauth/{repository/RefreshTokenRepositoryTest.java => command/repository/RefreshTokenCommandRepositoryTest.java} (62%) create mode 100644 backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java rename backend/baton/src/test/java/touch/baton/domain/oauth/{service/OauthServiceUpdateTest.java => command/service/OauthCommandServiceUpdateTest.java} (62%) rename backend/baton/src/test/java/touch/baton/domain/oauth/{ => command}/token/RefreshTokenTest.java (88%) rename backend/baton/src/test/java/touch/baton/domain/runner/repository/{RunnerRepositoryTest.java => RunnerQueryRepositoryTest.java} (71%) rename backend/baton/src/test/java/touch/baton/domain/runner/service/{RunnerServiceUpdateTest.java => RunnerCommandServiceTest.java} (65%) rename backend/baton/src/test/java/touch/baton/domain/runner/service/{RunnerServiceReadTest.java => RunnerQueryServiceTest.java} (67%) create mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostsApplicantCountTest.java rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/exception/validator/UrlValidatorTest.java (98%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/repository/RunnerPostRepositoryDeleteTest.java (53%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{service/RunnerPostServiceCreateTest.java => command/service/RunnerPostCommandServiceCreateTest.java} (54%) create mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{service/RunnerPostServiceUpdateTest.java => command/service/RunnerPostCommandServiceUpdateTest.java} (52%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/service/RunnerPostUpdateApplicantCancelationServiceTest.java (51%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/vo/CuriousContentsTest.java (89%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/vo/DeadlineTest.java (88%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/vo/ImplementedContentsTest.java (89%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/vo/IsReviewedTest.java (90%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/vo/PostscriptContentsTest.java (89%) rename backend/baton/src/test/java/touch/baton/domain/runnerpost/{ => command}/vo/PullRequestUrlTest.java (89%) create mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryReadTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepositoryTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryReadTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/SupporterRunnerPostRepositoryReadTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/read/RunnerPostRepositoryReadTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostReadServiceTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceDeleteTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java rename backend/baton/src/test/java/touch/baton/domain/supporter/{ => command}/SupporterFeedbackTest.java (90%) rename backend/baton/src/test/java/touch/baton/domain/supporter/{ => command}/SupporterTest.java (84%) rename backend/baton/src/test/java/touch/baton/domain/supporter/{repository/SupporterRepositoryTest.java => command/repository/SupporterQueryRepositoryTest.java} (61%) create mode 100644 backend/baton/src/test/java/touch/baton/domain/supporter/command/repository/SupporterRunnerPostCommandRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/supporter/command/service/SupporterCommandServiceTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/supporter/query/repository/SupporterRunnerPostQueryRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/supporter/query/service/SupporterQueryServiceTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java rename backend/baton/src/test/java/touch/baton/domain/tag/{ => command}/RunnerPostTagTest.java (76%) rename backend/baton/src/test/java/touch/baton/domain/tag/{ => command}/RunnerPostTagsTest.java (73%) rename backend/baton/src/test/java/touch/baton/domain/tag/{ => command}/TagTest.java (91%) rename backend/baton/src/test/java/touch/baton/domain/tag/{ => command}/vo/TagReducedNameTest.java (97%) create mode 100644 backend/baton/src/test/java/touch/baton/domain/tag/query/repository/RunnerPostTagQueryRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/tag/query/repository/TagQuerydslRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/tag/query/service/TagQueryServiceTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/tag/repository/RunnerPostTagRepositoryTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/tag/repository/TagRepositoryReadTest.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/tag/service/TagServiceReadTest.java rename backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/{SupporterTechnicalTagRepositoryTest.java => SupporterTechnicalTagQueryRepositoryTest.java} (75%) rename backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/{TechnicalTagRepositoryTest.java => TechnicalTagQueryRepositoryTest.java} (67%) create mode 100644 backend/baton/src/test/java/touch/baton/fixture/domain/NotificationFixture.java create mode 100644 backend/baton/src/test/java/touch/baton/fixture/vo/NotificationMessageFixture.java create mode 100644 backend/baton/src/test/java/touch/baton/fixture/vo/NotificationReferencedIdFixture.java create mode 100644 backend/baton/src/test/java/touch/baton/fixture/vo/NotificationTitleFixture.java diff --git a/.github/workflows/deploy-be-ci-cd-push.yml b/.github/workflows/deploy-be-ci-cd-push.yml index c30277920..01e58b0a7 100644 --- a/.github/workflows/deploy-be-ci-cd-push.yml +++ b/.github/workflows/deploy-be-ci-cd-push.yml @@ -53,13 +53,9 @@ jobs: - name: Pull Latest Docker Image run: | sudo docker login --username ${{ secrets.DOCKERHUB_DEPLOY_USERNAME }} --password ${{ secrets.DOCKERHUB_DEPLOY_TOKEN }} - if sudo docker inspect spring-baton &>/dev/null; then - sudo docker stop spring-baton - sudo docker rm -f spring-baton - sudo docker image prune -af - fi sudo docker pull 2023batondeploy/2023-baton-deploy:latest - name: Docker Compose run: | - sudo docker run --name spring-baton -v /home/ubuntu/logs:/app/logs -p 8080:8080 -e TZ=Asia/Seoul 2023batondeploy/2023-baton-deploy:latest 1>> build.log 2>> error.log & + /home/ubuntu/zero-downtime-deploy.sh + sudo docker image prune -af diff --git a/.github/workflows/dev-be-ci-cd-push.yml b/.github/workflows/dev-be-ci-cd-push.yml index b4d331781..62a461ff1 100644 --- a/.github/workflows/dev-be-ci-cd-push.yml +++ b/.github/workflows/dev-be-ci-cd-push.yml @@ -50,16 +50,13 @@ jobs: needs: build steps: + - name: Pull Latest Docker Image run: | sudo docker login --username ${{ secrets.DOCKERHUB_DEV_USERNAME }} --password ${{ secrets.DOCKERHUB_DEV_TOKEN }} - if sudo docker inspect spring-baton &>/dev/null; then - sudo docker stop spring-baton - sudo docker rm -f spring-baton - sudo docker image prune -af - fi sudo docker pull 2023baton/2023baton:latest - name: Docker Compose run: | - sudo docker run --name spring-baton --network=baton -p 8080:8080 -e TZ=Asia/Seoul 2023baton/2023baton:latest 1>> build.log 2>> error.log & + /home/ubuntu/zero-downtime-deploy.sh + sudo docker image prune -af diff --git a/backend/baton/.gitignore b/backend/baton/.gitignore index 121ef1a81..7f4296e4d 100644 --- a/backend/baton/.gitignore +++ b/backend/baton/.gitignore @@ -180,4 +180,5 @@ gradle-app.setting src/main/resources/application-deploy.yml src/main/resources/application-dev.yml +src/main/resources/application-local.yml src/main/resources/static/docs/** diff --git a/backend/baton/build.gradle b/backend/baton/build.gradle index 58d99a37d..559beafe2 100644 --- a/backend/baton/build.gradle +++ b/backend/baton/build.gradle @@ -46,6 +46,12 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-aop:3.1.1' + // Querydsl 추가 + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // flyway implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' diff --git a/backend/baton/secret b/backend/baton/secret index a25644c54..ca9b453e7 160000 --- a/backend/baton/secret +++ b/backend/baton/secret @@ -1 +1 @@ -Subproject commit a25644c54c052d55ea15f41a4e41221ee34474ed +Subproject commit ca9b453e7e8e730dc6fb2ecf4431d6aa48cc2a5e diff --git a/backend/baton/src/docs/asciidoc/GithubOauthApi.adoc b/backend/baton/src/docs/asciidoc/GithubOauthApi.adoc index ab6c85a3c..0f5ca4360 100644 --- a/backend/baton/src/docs/asciidoc/GithubOauthApi.adoc +++ b/backend/baton/src/docs/asciidoc/GithubOauthApi.adoc @@ -14,24 +14,16 @@ endif::[] ===== *Http Request* -include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_login/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_redirect_auth_code/http-request.adoc[] ===== *Http Request Path Paramemters* -include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_login/path-parameters.adoc[] - -===== *Http Request Query Paramemters* - -include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_login/query-parameters.adoc[] +include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_redirect_auth_code/path-parameters.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_login/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_redirect_auth_code/http-response.adoc[] ===== *Http Response Headers* -include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_login/response-headers.adoc[] - -===== *Http Response Cookies* - -include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_login/response-cookies.adoc[] +include::{snippets}/../../build/generated-snippets/github-oauth-api-test/github_redirect_auth_code/response-headers.adoc[] diff --git a/backend/baton/src/docs/asciidoc/NotificationDeleteApi.adoc b/backend/baton/src/docs/asciidoc/NotificationDeleteApi.adoc new file mode 100644 index 000000000..6510a9940 --- /dev/null +++ b/backend/baton/src/docs/asciidoc/NotificationDeleteApi.adoc @@ -0,0 +1,29 @@ +ifndef::snippets[] +:snippets: ../../../build/generated-snippets +endif::[] +:doctype: book +:icons: font +:source-highlighter: highlight.js +:toc: left +:toclevels: 3 +:sectlinks: +:operation-http-request-title: Example Request +:operation-http-response-title: Example Response + +==== *알림 삭제 API* + +===== *Http Request* + +include::{snippets}/../../build/generated-snippets/notification-delete-api-test/delete-notification-by-notification-id/http-request.adoc[] + +===== *Http Request Headers* + +include::{snippets}/../../build/generated-snippets/notification-delete-api-test/delete-notification-by-notification-id/request-headers.adoc[] + +===== *Http Request Path Parameters* + +include::{snippets}/../../build/generated-snippets/notification-delete-api-test/delete-notification-by-notification-id/path-parameters.adoc[] + +===== *Http Response* + +include::{snippets}/../../build/generated-snippets/notification-delete-api-test/delete-notification-by-notification-id/http-response.adoc[] diff --git a/backend/baton/src/docs/asciidoc/NotificationLoginReadApi.adoc b/backend/baton/src/docs/asciidoc/NotificationLoginReadApi.adoc new file mode 100644 index 000000000..6102b83fa --- /dev/null +++ b/backend/baton/src/docs/asciidoc/NotificationLoginReadApi.adoc @@ -0,0 +1,29 @@ +ifndef::snippets[] +:snippets: ../../../build/generated-snippets +endif::[] +:doctype: book +:icons: font +:source-highlighter: highlight.js +:toc: left +:toclevels: 3 +:sectlinks: +:operation-http-request-title: Example Request +:operation-http-response-title: Example Response + +==== *로그인된 사용자 알림 목록 조회 API* + +===== *Http Request* + +include::{snippets}/../../build/generated-snippets/notification-read-with-logined-member-api-test/read-notifications-by-member-id/http-request.adoc[] + +===== *Http Request Headers* + +include::{snippets}/../../build/generated-snippets/notification-read-with-logined-member-api-test/read-notifications-by-member-id/request-headers.adoc[] + +===== *Http Response* + +include::{snippets}/../../build/generated-snippets/notification-read-with-logined-member-api-test/read-notifications-by-member-id/http-response.adoc[] + +===== *Http Response Fields* + +include::{snippets}/../../build/generated-snippets/notification-read-with-logined-member-api-test/read-notifications-by-member-id/response-fields.adoc[] diff --git a/backend/baton/src/docs/asciidoc/NotificationUpdateApi.adoc b/backend/baton/src/docs/asciidoc/NotificationUpdateApi.adoc new file mode 100644 index 000000000..90226c48a --- /dev/null +++ b/backend/baton/src/docs/asciidoc/NotificationUpdateApi.adoc @@ -0,0 +1,29 @@ +ifndef::snippets[] +:snippets: ../../../build/generated-snippets +endif::[] +:doctype: book +:icons: font +:source-highlighter: highlight.js +:toc: left +:toclevels: 3 +:sectlinks: +:operation-http-request-title: Example Request +:operation-http-response-title: Example Response + +==== *알림 읽음 여부 업데이트 API* + +===== *Http Request* + +include::{snippets}/../../build/generated-snippets/notification-update-api-test/update-notification-is-read-true-by-member/http-request.adoc[] + +===== *Http Request Headers* + +include::{snippets}/../../build/generated-snippets/notification-update-api-test/update-notification-is-read-true-by-member/request-headers.adoc[] + +===== *Http Request Path Parameters* + +include::{snippets}/../../build/generated-snippets/notification-update-api-test/update-notification-is-read-true-by-member/path-parameters.adoc[] + +===== *Http Response* + +include::{snippets}/../../build/generated-snippets/notification-update-api-test/update-notification-is-read-true-by-member/http-response.adoc[] diff --git a/backend/baton/src/docs/asciidoc/OauthLogoutApi.adoc b/backend/baton/src/docs/asciidoc/OauthLogoutApi.adoc new file mode 100644 index 000000000..2cf491c42 --- /dev/null +++ b/backend/baton/src/docs/asciidoc/OauthLogoutApi.adoc @@ -0,0 +1,25 @@ +ifndef::snippets[] +:snippets: ../../../build/generated-snippets +endif::[] +:doctype: book +:icons: font +:source-highlighter: highlight.js +:toc: left +:toclevels: 3 +:sectlinks: +:operation-http-request-title: Example Request +:operation-http-response-title: Example Response + +==== *로그아웃 API* + +===== *Http Request* + +include::{snippets}/../../build/generated-snippets/oauth-logout-api-test/logout/http-request.adoc[] + +===== *Http Request Headers* + +include::{snippets}/../../build/generated-snippets/oauth-logout-api-test/logout/request-headers.adoc[] + +===== *Http Response* + +include::{snippets}/../../build/generated-snippets/oauth-logout-api-test/logout/http-response.adoc[] diff --git a/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc b/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc index ba10d0571..6a427755c 100644 --- a/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc +++ b/backend/baton/src/docs/asciidoc/RunnerPostReadApi.adoc @@ -32,45 +32,49 @@ include::{snippets}/../../build/generated-snippets/runner-post-read-one-api-test include::{snippets}/../../build/generated-snippets/runner-post-read-one-api-test/read-by-runner-post-id/response-fields.adoc[] -==== *러너 게시글 전체 조회 API* +==== *러너 마이페이지 게시글 조회 API* ===== *Http Request* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-posts-by-review-status/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-runner-api-test/read-runner-post-by-logined-runner-and-review-status/http-request.adoc[] + +===== *Http Request Headers* + +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-runner-api-test/read-runner-post-by-logined-runner-and-review-status/request-headers.adoc[] ===== *Http Request Query Parameters* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-posts-by-review-status/query-parameters.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-runner-api-test/read-runner-post-by-logined-runner-and-review-status/query-parameters.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-posts-by-review-status/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-runner-api-test/read-runner-post-by-logined-runner-and-review-status/http-response.adoc[] ===== *Http Response Fields* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-posts-by-review-status/response-fields.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-runner-api-test/read-runner-post-by-logined-runner-and-review-status/response-fields.adoc[] -==== *러너 마이페이지 게시글 조회 API* +==== *러너 마이페이지 게시글 개수 조회 API* ===== *Http Request* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-my-page/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-runner-api-test/count-runner-post-by-logined-runner-and-review-status/http-request.adoc[] ===== *Http Request Headers* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-my-page/request-headers.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-runner-api-test/count-runner-post-by-logined-runner-and-review-status/request-headers.adoc[] ===== *Http Request Query Parameters* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-my-page/query-parameters.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-runner-api-test/count-runner-post-by-logined-runner-and-review-status/query-parameters.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-my-page/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-runner-api-test/count-runner-post-by-logined-runner-and-review-status/http-response.adoc[] ===== *Http Response Fields* -include::{snippets}/../../build/generated-snippets/runner-post-read-all-api-test/read-runner-my-page/response-fields.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-runner-api-test/count-runner-post-by-logined-runner-and-review-status/response-fields.adoc[] ==== *리뷰 지원한 서포터 조회 API* @@ -98,37 +102,77 @@ include::{snippets}/../../build/generated-snippets/supporter-runner-post-read-ap ===== *Http Request* -include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-posts-by-logined-supporter-and-review-status/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-post-by-logined-supporter-and-review-status/http-request.adoc[] + +===== *Http Request Query Parameters* + +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-post-by-logined-supporter-and-review-status/query-parameters.adoc[] + +===== *Http Response* + +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-post-by-logined-supporter-and-review-status/http-response.adoc[] + +===== *Http Response Fields* + +include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-post-by-logined-supporter-and-review-status/response-fields.adoc[] + +==== *서포터 마이페이지 게시글 개수 조회 API* + +===== *Http Request* + +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-supporter-api-test/count-runner-post-by-logined-supporter-and-review-status/http-request.adoc[] + +===== *Http Request Headers* + +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-supporter-api-test/count-runner-post-by-logined-supporter-and-review-status/request-headers.adoc[] + +===== *Http Request Query Parameters* + +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-supporter-api-test/count-runner-post-by-logined-supporter-and-review-status/query-parameters.adoc[] + +===== *Http Response* + +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-supporter-api-test/count-runner-post-by-logined-supporter-and-review-status/http-response.adoc[] + +===== *Http Response Fields* + +include::{snippets}/../../build/generated-snippets/runner-post-count-with-logined-supporter-api-test/count-runner-post-by-logined-supporter-and-review-status/response-fields.adoc[] + +==== *서포터의 리뷰 완료한 게시글 조회 API* + +===== *Http Request* + +include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-runner-post-by-supporter-id-and-review-status/http-request.adoc[] ===== *Http Request Query Parameters* -include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-posts-by-logined-supporter-and-review-status/query-parameters.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-runner-post-by-supporter-id-and-review-status/query-parameters.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-posts-by-logined-supporter-and-review-status/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-runner-post-by-supporter-id-and-review-status/http-response.adoc[] ===== *Http Response Fields* -include::{snippets}/../../build/generated-snippets/runner-post-read-with-logined-supporter-api-test/read-runner-posts-by-logined-supporter-and-review-status/response-fields.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-runner-post-by-supporter-id-and-review-status/response-fields.adoc[] -==== *서포터 리뷰 완료한 게시글 조회 API* +==== *서포터의 리뷰 완료한 게시글 개수 조회 API* ===== *Http Request* -include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-referenced-by-supporter/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-of-supporter-by-guest-api-test/count-runner-post-by-supporter-id-and-review-status/http-request.adoc[] ===== *Http Request Query Parameters* -include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-referenced-by-supporter/query-parameters.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-of-supporter-by-guest-api-test/count-runner-post-by-supporter-id-and-review-status/query-parameters.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-referenced-by-supporter/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-of-supporter-by-guest-api-test/count-runner-post-by-supporter-id-and-review-status/http-response.adoc[] ===== *Http Response Fields* -include::{snippets}/../../build/generated-snippets/runner-post-read-of-supporter-by-guest-api-test/read-referenced-by-supporter/response-fields.adoc[] +include::{snippets}/../../build/generated-snippets/runner-post-count-of-supporter-by-guest-api-test/count-runner-post-by-supporter-id-and-review-status/response-fields.adoc[] ==== *태그 이름과 리뷰 상태를 조건으로 러너 게시글 페이징 조회 API* @@ -152,16 +196,16 @@ include::{snippets}/../../build/generated-snippets/runner-post-read-search-api-t ===== *Http Request* -include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags-by-reduced-name/http-request.adoc[] ===== *Http Request Query Parameters* -include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags/query-parameters.adoc[] +include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags-by-reduced-name/query-parameters.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags-by-reduced-name/http-response.adoc[] ===== *Http Response Fields* -include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags/response-fields.adoc[] +include::{snippets}/../../build/generated-snippets/tag-read-api-test/read-tags-by-reduced-name/response-fields.adoc[] diff --git a/backend/baton/src/docs/asciidoc/RunnerReadApi.adoc b/backend/baton/src/docs/asciidoc/RunnerReadApi.adoc index 0a57d96bb..2a2619fb6 100644 --- a/backend/baton/src/docs/asciidoc/RunnerReadApi.adoc +++ b/backend/baton/src/docs/asciidoc/RunnerReadApi.adoc @@ -14,13 +14,13 @@ endif::[] ===== *Http Request* -include::{snippets}/../../build/generated-snippets/runner-read-by-runner-id-api-test/read-runner-profile/http-request.adoc[] +include::{snippets}/../../build/generated-snippets/runner-read-by-guest-api-test/read-runner-profile/http-request.adoc[] ===== *Http Response* -include::{snippets}/../../build/generated-snippets/runner-read-by-runner-id-api-test/read-runner-profile/http-response.adoc[] +include::{snippets}/../../build/generated-snippets/runner-read-by-guest-api-test/read-runner-profile/http-response.adoc[] ===== *Http Response Fields* -include::{snippets}/../../build/generated-snippets/runner-read-by-runner-id-api-test/read-runner-profile/response-fields.adoc[] +include::{snippets}/../../build/generated-snippets/runner-read-by-guest-api-test/read-runner-profile/response-fields.adoc[] diff --git a/backend/baton/src/docs/asciidoc/index.adoc b/backend/baton/src/docs/asciidoc/index.adoc index fc596cefa..ce5430092 100644 --- a/backend/baton/src/docs/asciidoc/index.adoc +++ b/backend/baton/src/docs/asciidoc/index.adoc @@ -19,6 +19,7 @@ include::GithubBranchCreateApi.adoc[] == *[ 로그인 ]* include::GithubOauthApi.adoc[] +include::OauthLogoutApi.adoc[] include::RefreshTokenApi.adoc[] == *[ 프로필 ]* @@ -63,3 +64,17 @@ include::RunnerPostUpdateApplicantCancelationApi.adoc[] === *러너 게시글 삭제* include::RunnerPostDeleteApi.adoc[] + +== *[ 알림 ]* + +=== *알림 조회* + +include::NotificationLoginReadApi.adoc[] + +=== *알림 수정* + +include::NotificationUpdateApi.adoc[] + +=== *알림 삭제* + +include::NotificationDeleteApi.adoc[] diff --git a/backend/baton/src/main/java/touch/baton/common/schedule/ScheduleRunnerPostRepository.java b/backend/baton/src/main/java/touch/baton/common/schedule/ScheduleRunnerPostRepository.java index e877b034a..fc4007d90 100644 --- a/backend/baton/src/main/java/touch/baton/common/schedule/ScheduleRunnerPostRepository.java +++ b/backend/baton/src/main/java/touch/baton/common/schedule/ScheduleRunnerPostRepository.java @@ -3,7 +3,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import touch.baton.domain.runnerpost.RunnerPost; +import touch.baton.domain.runnerpost.command.RunnerPost; public interface ScheduleRunnerPostRepository extends JpaRepository { diff --git a/backend/baton/src/main/java/touch/baton/config/ArgumentResolverConfig.java b/backend/baton/src/main/java/touch/baton/config/ArgumentResolverConfig.java index 4173249d0..a8bbe56fe 100644 --- a/backend/baton/src/main/java/touch/baton/config/ArgumentResolverConfig.java +++ b/backend/baton/src/main/java/touch/baton/config/ArgumentResolverConfig.java @@ -3,9 +3,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import touch.baton.domain.oauth.controller.resolver.AuthMemberPrincipalArgumentResolver; -import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipalArgumentResolver; -import touch.baton.domain.oauth.controller.resolver.AuthSupporterPrincipalArgumentResolver; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipalArgumentResolver; +import touch.baton.domain.oauth.query.controller.resolver.AuthRunnerPrincipalArgumentResolver; +import touch.baton.domain.oauth.query.controller.resolver.AuthSupporterPrincipalArgumentResolver; import java.util.List; diff --git a/backend/baton/src/main/java/touch/baton/config/QuerydslConfig.java b/backend/baton/src/main/java/touch/baton/config/QuerydslConfig.java new file mode 100644 index 000000000..7331d9d88 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/config/QuerydslConfig.java @@ -0,0 +1,19 @@ +package touch.baton.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/backend/baton/src/main/java/touch/baton/config/WebMvcConfig.java b/backend/baton/src/main/java/touch/baton/config/WebMvcConfig.java index 0b2e32481..0bba42c5b 100644 --- a/backend/baton/src/main/java/touch/baton/config/WebMvcConfig.java +++ b/backend/baton/src/main/java/touch/baton/config/WebMvcConfig.java @@ -9,12 +9,7 @@ import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.HttpHeaders.LOCATION; -import static org.springframework.http.HttpMethod.DELETE; -import static org.springframework.http.HttpMethod.GET; -import static org.springframework.http.HttpMethod.OPTIONS; -import static org.springframework.http.HttpMethod.PATCH; -import static org.springframework.http.HttpMethod.POST; -import static org.springframework.http.HttpMethod.PUT; +import static org.springframework.http.HttpMethod.*; @Configuration public class WebMvcConfig implements WebMvcConfigurer { diff --git a/backend/baton/src/main/java/touch/baton/config/converter/OauthTypeConverter.java b/backend/baton/src/main/java/touch/baton/config/converter/OauthTypeConverter.java index be414a79f..15e81b805 100644 --- a/backend/baton/src/main/java/touch/baton/config/converter/OauthTypeConverter.java +++ b/backend/baton/src/main/java/touch/baton/config/converter/OauthTypeConverter.java @@ -1,7 +1,7 @@ package touch.baton.config.converter; import org.springframework.core.convert.converter.Converter; -import touch.baton.domain.oauth.OauthType; +import touch.baton.domain.oauth.command.OauthType; public class OauthTypeConverter implements Converter { diff --git a/backend/baton/src/main/java/touch/baton/config/converter/ReviewStatusConverter.java b/backend/baton/src/main/java/touch/baton/config/converter/ReviewStatusConverter.java index 36cdfcff3..3fa245b0a 100644 --- a/backend/baton/src/main/java/touch/baton/config/converter/ReviewStatusConverter.java +++ b/backend/baton/src/main/java/touch/baton/config/converter/ReviewStatusConverter.java @@ -1,7 +1,7 @@ package touch.baton.config.converter; import org.springframework.core.convert.converter.Converter; -import touch.baton.domain.runnerpost.vo.ReviewStatus; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; public class ReviewStatusConverter implements Converter { diff --git a/backend/baton/src/main/java/touch/baton/config/filter/FilterConfig.java b/backend/baton/src/main/java/touch/baton/config/filter/FilterConfig.java index f40bb5064..4328e9c8f 100644 --- a/backend/baton/src/main/java/touch/baton/config/filter/FilterConfig.java +++ b/backend/baton/src/main/java/touch/baton/config/filter/FilterConfig.java @@ -15,7 +15,7 @@ public class FilterConfig implements WebMvcConfigurer { public FilterRegistrationBean getFilterRegistrationBean() { final FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(new MDCLoggingFilter()); registrationBean.setOrder(Integer.MIN_VALUE); - registrationBean.setUrlPatterns(List.of("/api/**")); + registrationBean.setUrlPatterns(List.of("/api/*")); return registrationBean; } } diff --git a/backend/baton/src/main/java/touch/baton/config/filter/MDCLoggingFilter.java b/backend/baton/src/main/java/touch/baton/config/filter/MDCLoggingFilter.java index e7d46b854..7e287fbe5 100644 --- a/backend/baton/src/main/java/touch/baton/config/filter/MDCLoggingFilter.java +++ b/backend/baton/src/main/java/touch/baton/config/filter/MDCLoggingFilter.java @@ -5,6 +5,8 @@ import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.core.config.Order; import org.slf4j.MDC; import org.springframework.core.Ordered; @@ -17,8 +19,9 @@ class MDCLoggingFilter implements Filter { @Override public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { - final UUID uuid = UUID.randomUUID(); - MDC.put("request_id", uuid.toString()); + final String requestId = ((HttpServletRequest) servletRequest).getHeader("X-Request-ID"); + + MDC.put("request_id", StringUtils.defaultString(requestId, UUID.randomUUID().toString())); filterChain.doFilter(servletRequest, servletResponse); MDC.clear(); } diff --git a/backend/baton/src/main/java/touch/baton/domain/common/TruncatedBaseEntity.java b/backend/baton/src/main/java/touch/baton/domain/common/TruncatedBaseEntity.java new file mode 100644 index 000000000..205b807ef --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/common/TruncatedBaseEntity.java @@ -0,0 +1,22 @@ +package touch.baton.domain.common; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; + +public abstract class TruncatedBaseEntity extends BaseEntity { + + @Override + public LocalDateTime getCreatedAt() { + return super.getCreatedAt().truncatedTo(ChronoUnit.MINUTES); + } + + @Override + public LocalDateTime getDeletedAt() { + return super.getDeletedAt().truncatedTo(ChronoUnit.MINUTES); + } + + @Override + public LocalDateTime getUpdatedAt() { + return super.getUpdatedAt().truncatedTo(ChronoUnit.MINUTES); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java b/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java index 1ab1a0a92..06e6bdb53 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java +++ b/backend/baton/src/main/java/touch/baton/domain/common/exception/ClientErrorCode.java @@ -17,6 +17,7 @@ public enum ClientErrorCode { PULL_REQUEST_URL_IS_NOT_URL(HttpStatus.BAD_REQUEST, "RP011", "올바른 PR 주소를 입력해주세요."), CURIOUS_CONTENTS_ARE_NULL(HttpStatus.BAD_REQUEST, "RP012", "궁금한 내용을 입력해주세요."), POSTSCRIPT_CONTENTS_ARE_NULL(HttpStatus.BAD_REQUEST, "RP013", "참고 사항을 입력해주세요."), + INVALID_QUERY_STRING_FORMAT(HttpStatus.BAD_REQUEST, "RP014", "잘못된 Query Parameter 형식입니다."), REVIEW_TYPE_IS_NULL(HttpStatus.BAD_REQUEST, "FB001", "만족도를 입력해주세요."), SUPPORTER_ID_IS_NULL(HttpStatus.BAD_REQUEST, "FB002", "서포터 식별자를 입력해주세요."), diff --git a/backend/baton/src/main/java/touch/baton/domain/common/request/PageParams.java b/backend/baton/src/main/java/touch/baton/domain/common/request/PageParams.java new file mode 100644 index 000000000..07446ed03 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/common/request/PageParams.java @@ -0,0 +1,14 @@ +package touch.baton.domain.common.request; + +import touch.baton.domain.common.exception.validator.ValidNotNull; + +import static touch.baton.domain.common.exception.ClientErrorCode.INVALID_QUERY_STRING_FORMAT; + +public record PageParams(Long cursor, @ValidNotNull(clientErrorCode = INVALID_QUERY_STRING_FORMAT) Integer limit) { + + private static final int ADDITIONAL_QUERY_DATA_COUNT = 1; + + public Integer getLimitForQuery() { + return limit + ADDITIONAL_QUERY_DATA_COUNT; + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/common/response/IdExtractable.java b/backend/baton/src/main/java/touch/baton/domain/common/response/IdExtractable.java new file mode 100644 index 000000000..12e320b3d --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/common/response/IdExtractable.java @@ -0,0 +1,6 @@ +package touch.baton.domain.common.response; + +public interface IdExtractable { + + Long extractId(); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java b/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java index 7f0a1f5d6..74df1330c 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java @@ -1,38 +1,37 @@ package touch.baton.domain.common.response; -import org.springframework.data.domain.Page; +import touch.baton.domain.common.request.PageParams; import java.util.List; -public record PageResponse(List data, - PageInfo pageInfo -) { +public record PageResponse(List data, PageInfo pageInfo) { - public static PageResponse from(final Page page) { - return new PageResponse<>(page.getContent(), PageInfo.from(page)); + public static PageResponse of(final List responses, final PageParams pageParams) { + final int limit = pageParams.limit(); + if (isLastPage(responses, limit)) { + return new PageResponse<>(responses, PageInfo.last()); + } + final List limitResponses = responses.subList(0, pageParams.getLimitForQuery()); + return new PageResponse<>(limitResponses, PageInfo.normal(getLastElementId(limitResponses))); } - public record PageInfo(boolean isFirst, - boolean isLast, - boolean hasNext, - int totalPages, - long totalElements, - int currentPage, - int currentSize - ) { - - private static final int START_PAGE_NUMBER = 1; - - public static PageInfo from(final Page page) { - return new PageInfo( - page.isFirst(), - page.isLast(), - page.hasNext(), - page.getTotalPages(), - page.getTotalElements(), - page.getNumber() + START_PAGE_NUMBER, - page.getSize() - ); + public record PageInfo(boolean isLast, Long nextCursor) { + + public static PageInfo last() { + return new PageInfo(true, null); + } + + public static PageInfo normal(final Long nextCursor) { + return new PageInfo(false, nextCursor); } } + + private static boolean isLastPage(final List responses, final int limit) { + return responses.size() <= limit; + } + + private static Long getLastElementId(final List responses) { + final int lastIndex = responses.size() - 1; + return responses.get(lastIndex).extractId(); + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/common/vo/ChattingCount.java b/backend/baton/src/main/java/touch/baton/domain/common/vo/ChattingCount.java deleted file mode 100644 index 072f9d222..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/common/vo/ChattingCount.java +++ /dev/null @@ -1,31 +0,0 @@ -package touch.baton.domain.common.vo; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.ColumnDefault; - -import static lombok.AccessLevel.PROTECTED; - -@EqualsAndHashCode -@Getter -@NoArgsConstructor(access = PROTECTED) -@Embeddable -public class ChattingCount { - - private static final String DEFAULT_VALUE = "0"; - - @ColumnDefault(DEFAULT_VALUE) - @Column(name = "chatting_room_count", nullable = false) - private int value; - - public ChattingCount(final int value) { - this.value = value; - } - - public static ChattingCount zero() { - return new ChattingCount(Integer.parseInt(DEFAULT_VALUE)); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/common/vo/TotalRating.java b/backend/baton/src/main/java/touch/baton/domain/common/vo/TotalRating.java deleted file mode 100644 index 8db33b994..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/common/vo/TotalRating.java +++ /dev/null @@ -1,27 +0,0 @@ -package touch.baton.domain.common.vo; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.ColumnDefault; - -import static lombok.AccessLevel.PROTECTED; - -@EqualsAndHashCode -@Getter -@NoArgsConstructor(access = PROTECTED) -@Embeddable -public class TotalRating { - - private static final String DEFAULT_VALUE = "0"; - - @ColumnDefault(DEFAULT_VALUE) - @Column(name = "total_rating", nullable = false) - private int value; - - public TotalRating(final int value) { - this.value = value; - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/SupporterFeedback.java b/backend/baton/src/main/java/touch/baton/domain/feedback/command/SupporterFeedback.java similarity index 91% rename from backend/baton/src/main/java/touch/baton/domain/feedback/SupporterFeedback.java rename to backend/baton/src/main/java/touch/baton/domain/feedback/command/SupporterFeedback.java index 09b201005..7cfc4cb97 100644 --- a/backend/baton/src/main/java/touch/baton/domain/feedback/SupporterFeedback.java +++ b/backend/baton/src/main/java/touch/baton/domain/feedback/command/SupporterFeedback.java @@ -1,4 +1,4 @@ -package touch.baton.domain.feedback; +package touch.baton.domain.feedback.command; import jakarta.persistence.Column; import jakarta.persistence.Embedded; @@ -14,12 +14,12 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.BaseEntity; +import touch.baton.domain.feedback.command.vo.Description; +import touch.baton.domain.feedback.command.vo.ReviewType; import touch.baton.domain.feedback.exception.SupporterFeedbackException; -import touch.baton.domain.feedback.vo.Description; -import touch.baton.domain.feedback.vo.ReviewType; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/controller/FeedbackController.java b/backend/baton/src/main/java/touch/baton/domain/feedback/command/controller/FeedbackCommandController.java similarity index 65% rename from backend/baton/src/main/java/touch/baton/domain/feedback/controller/FeedbackController.java rename to backend/baton/src/main/java/touch/baton/domain/feedback/command/controller/FeedbackCommandController.java index c09123426..12aa3391e 100644 --- a/backend/baton/src/main/java/touch/baton/domain/feedback/controller/FeedbackController.java +++ b/backend/baton/src/main/java/touch/baton/domain/feedback/command/controller/FeedbackCommandController.java @@ -1,4 +1,4 @@ -package touch.baton.domain.feedback.controller; +package touch.baton.domain.feedback.command.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -8,25 +8,25 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder; -import touch.baton.domain.feedback.service.FeedbackService; -import touch.baton.domain.feedback.service.SupporterFeedBackCreateRequest; -import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipal; -import touch.baton.domain.runner.Runner; +import touch.baton.domain.feedback.command.service.FeedbackCommandService; +import touch.baton.domain.feedback.command.service.dto.SupporterFeedBackCreateRequest; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.oauth.query.controller.resolver.AuthRunnerPrincipal; import java.net.URI; @RequiredArgsConstructor @RequestMapping("/api/v1/feedback") @RestController -public class FeedbackController { +public class FeedbackCommandController { - private final FeedbackService feedbackService; + private final FeedbackCommandService feedbackCommandService; @PostMapping("/supporter") public ResponseEntity createSupporterFeedback(@AuthRunnerPrincipal final Runner runner, @Valid @RequestBody final SupporterFeedBackCreateRequest request ) { - final Long savedId = feedbackService.createSupporterFeedback(runner, request); + final Long savedId = feedbackCommandService.createSupporterFeedback(runner, request); final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/feedback/supporter") .path("/{id}") diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/command/repository/SupporterFeedbackCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/feedback/command/repository/SupporterFeedbackCommandRepository.java new file mode 100644 index 000000000..b03f39ca2 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/feedback/command/repository/SupporterFeedbackCommandRepository.java @@ -0,0 +1,9 @@ +package touch.baton.domain.feedback.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.feedback.command.SupporterFeedback; + +public interface SupporterFeedbackCommandRepository extends JpaRepository { + + boolean existsByRunnerPostIdAndSupporterId(final Long runnerPostId, final Long supporterId); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/service/FeedbackService.java b/backend/baton/src/main/java/touch/baton/domain/feedback/command/service/FeedbackCommandService.java similarity index 53% rename from backend/baton/src/main/java/touch/baton/domain/feedback/service/FeedbackService.java rename to backend/baton/src/main/java/touch/baton/domain/feedback/command/service/FeedbackCommandService.java index 37e872239..326940adf 100644 --- a/backend/baton/src/main/java/touch/baton/domain/feedback/service/FeedbackService.java +++ b/backend/baton/src/main/java/touch/baton/domain/feedback/command/service/FeedbackCommandService.java @@ -1,38 +1,38 @@ -package touch.baton.domain.feedback.service; +package touch.baton.domain.feedback.command.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.feedback.SupporterFeedback; +import touch.baton.domain.feedback.command.SupporterFeedback; +import touch.baton.domain.feedback.command.repository.SupporterFeedbackCommandRepository; +import touch.baton.domain.feedback.command.service.dto.SupporterFeedBackCreateRequest; +import touch.baton.domain.feedback.command.vo.Description; +import touch.baton.domain.feedback.command.vo.ReviewType; import touch.baton.domain.feedback.exception.FeedbackBusinessException; -import touch.baton.domain.feedback.repository.SupporterFeedbackRepository; -import touch.baton.domain.feedback.vo.Description; -import touch.baton.domain.feedback.vo.ReviewType; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.RunnerPostRepository; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.repository.SupporterRepository; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.repository.SupporterCommandRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.repository.RunnerPostCommandRepository; @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional @Service -public class FeedbackService { +public class FeedbackCommandService { private static final String DELIMITER = "|"; - private final SupporterFeedbackRepository supporterFeedbackRepository; - private final RunnerPostRepository runnerPostRepository; - private final SupporterRepository supporterRepository; + private final SupporterFeedbackCommandRepository supporterFeedbackCommandRepository; + private final RunnerPostCommandRepository runnerPostCommandRepository; + private final SupporterCommandRepository supporterCommandRepository; - @Transactional public Long createSupporterFeedback(final Runner runner, final SupporterFeedBackCreateRequest request) { - final Supporter foundSupporter = supporterRepository.findById(request.supporterId()) + final Supporter foundSupporter = supporterCommandRepository.findById(request.supporterId()) .orElseThrow(() -> new FeedbackBusinessException("서포터를 찾을 수 없습니다.")); - final RunnerPost foundRunnerPost = runnerPostRepository.findById(request.runnerPostId()) + final RunnerPost foundRunnerPost = runnerPostCommandRepository.findById(request.runnerPostId()) .orElseThrow(() -> new FeedbackBusinessException("러너 게시글을 찾을 수 없습니다.")); - if (supporterFeedbackRepository.existsByRunnerPostIdAndSupporterId(foundRunnerPost.getId(), foundSupporter.getId())) { + if (supporterFeedbackCommandRepository.existsByRunnerPostIdAndSupporterId(foundRunnerPost.getId(), foundSupporter.getId())) { throw new FeedbackBusinessException("서포터에 대한 피드백을 작성했으면 추가적인 피드백을 남길 수 없습니다."); } if (foundRunnerPost.isNotOwner(runner)) { @@ -51,6 +51,6 @@ public Long createSupporterFeedback(final Runner runner, final SupporterFeedBack .runnerPost(foundRunnerPost) .build(); - return supporterFeedbackRepository.save(supporterFeedback).getId(); + return supporterFeedbackCommandRepository.save(supporterFeedback).getId(); } } diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/service/SupporterFeedBackCreateRequest.java b/backend/baton/src/main/java/touch/baton/domain/feedback/command/service/dto/SupporterFeedBackCreateRequest.java similarity index 93% rename from backend/baton/src/main/java/touch/baton/domain/feedback/service/SupporterFeedBackCreateRequest.java rename to backend/baton/src/main/java/touch/baton/domain/feedback/command/service/dto/SupporterFeedBackCreateRequest.java index c1f317153..7d4b78547 100644 --- a/backend/baton/src/main/java/touch/baton/domain/feedback/service/SupporterFeedBackCreateRequest.java +++ b/backend/baton/src/main/java/touch/baton/domain/feedback/command/service/dto/SupporterFeedBackCreateRequest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.feedback.service; +package touch.baton.domain.feedback.command.service.dto; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.validator.ValidNotNull; diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/vo/Description.java b/backend/baton/src/main/java/touch/baton/domain/feedback/command/vo/Description.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/feedback/vo/Description.java rename to backend/baton/src/main/java/touch/baton/domain/feedback/command/vo/Description.java index 5bfc58deb..31c56d47c 100644 --- a/backend/baton/src/main/java/touch/baton/domain/feedback/vo/Description.java +++ b/backend/baton/src/main/java/touch/baton/domain/feedback/command/vo/Description.java @@ -1,4 +1,4 @@ -package touch.baton.domain.feedback.vo; +package touch.baton.domain.feedback.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/vo/ReviewType.java b/backend/baton/src/main/java/touch/baton/domain/feedback/command/vo/ReviewType.java similarity index 51% rename from backend/baton/src/main/java/touch/baton/domain/feedback/vo/ReviewType.java rename to backend/baton/src/main/java/touch/baton/domain/feedback/command/vo/ReviewType.java index e8a0f1ca4..13e21bbc1 100644 --- a/backend/baton/src/main/java/touch/baton/domain/feedback/vo/ReviewType.java +++ b/backend/baton/src/main/java/touch/baton/domain/feedback/command/vo/ReviewType.java @@ -1,4 +1,4 @@ -package touch.baton.domain.feedback.vo; +package touch.baton.domain.feedback.command.vo; public enum ReviewType { diff --git a/backend/baton/src/main/java/touch/baton/domain/feedback/repository/SupporterFeedbackRepository.java b/backend/baton/src/main/java/touch/baton/domain/feedback/repository/SupporterFeedbackRepository.java deleted file mode 100644 index 66e62d5f7..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/feedback/repository/SupporterFeedbackRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package touch.baton.domain.feedback.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import touch.baton.domain.feedback.SupporterFeedback; - -public interface SupporterFeedbackRepository extends JpaRepository { - - boolean existsByRunnerPostIdAndSupporterId(final Long runnerPostId, final Long supporterId); -} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/Member.java b/backend/baton/src/main/java/touch/baton/domain/member/command/Member.java similarity index 91% rename from backend/baton/src/main/java/touch/baton/domain/member/Member.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/Member.java index 61925a91b..4a97ae168 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/Member.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/Member.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member; +package touch.baton.domain.member.command; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -8,13 +8,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.BaseEntity; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; import touch.baton.domain.member.exception.MemberDomainException; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/Runner.java b/backend/baton/src/main/java/touch/baton/domain/member/command/Runner.java similarity index 86% rename from backend/baton/src/main/java/touch/baton/domain/runner/Runner.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/Runner.java index 8fe2489d6..befd0f2c0 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/Runner.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/Runner.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runner; +package touch.baton.domain.member.command; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -11,14 +11,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.BaseEntity; -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.runner.exception.RunnerDomainException; -import touch.baton.domain.supporter.exception.SupporterDomainException; -import touch.baton.domain.technicaltag.RunnerTechnicalTag; -import touch.baton.domain.technicaltag.RunnerTechnicalTags; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.Introduction; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.exception.RunnerDomainException; +import touch.baton.domain.member.exception.SupporterDomainException; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTag; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTags; import java.util.List; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/Supporter.java b/backend/baton/src/main/java/touch/baton/domain/member/command/Supporter.java similarity index 89% rename from backend/baton/src/main/java/touch/baton/domain/supporter/Supporter.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/Supporter.java index 7227d30ca..dc2473e77 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/Supporter.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/Supporter.java @@ -1,4 +1,4 @@ -package touch.baton.domain.supporter; +package touch.baton.domain.member.command; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -11,14 +11,13 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.BaseEntity; -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.supporter.exception.SupporterDomainException; -import touch.baton.domain.supporter.vo.ReviewCount; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.Introduction; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.member.exception.SupporterDomainException; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; import java.util.List; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/SupporterRunnerPost.java b/backend/baton/src/main/java/touch/baton/domain/member/command/SupporterRunnerPost.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/supporter/SupporterRunnerPost.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/SupporterRunnerPost.java index 33eda68df..36f3a01e1 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/SupporterRunnerPost.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/SupporterRunnerPost.java @@ -1,4 +1,4 @@ -package touch.baton.domain.supporter; +package touch.baton.domain.member.command; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -11,9 +11,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.BaseEntity; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.exception.RunnerPostDomainException; -import touch.baton.domain.supporter.vo.Message; +import touch.baton.domain.member.command.vo.Message; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.exception.RunnerPostDomainException; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/controller/MemberBranchController.java b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/MemberBranchController.java similarity index 73% rename from backend/baton/src/main/java/touch/baton/domain/member/controller/MemberBranchController.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/controller/MemberBranchController.java index b48dcea41..ed1170cac 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/controller/MemberBranchController.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/MemberBranchController.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.controller; +package touch.baton.domain.member.command.controller; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -7,13 +7,14 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.service.dto.GithubBranchManageable; -import touch.baton.domain.member.service.dto.GithubRepoNameRequest; -import touch.baton.domain.oauth.controller.resolver.AuthMemberPrincipal; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.service.GithubBranchManageable; +import touch.baton.domain.member.command.service.dto.GithubRepoNameRequest; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipal; import java.net.URI; +// FIXME: 2023/09/26 패키지 위치 변경 @RequiredArgsConstructor @RequestMapping("/api/v1/branch") @RestController diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/controller/RunnerCommandController.java b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/RunnerCommandController.java new file mode 100644 index 000000000..75fda80fb --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/RunnerCommandController.java @@ -0,0 +1,32 @@ +package touch.baton.domain.member.command.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +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.util.UriComponentsBuilder; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.service.RunnerCommandService; +import touch.baton.domain.member.command.service.dto.RunnerUpdateRequest; +import touch.baton.domain.oauth.query.controller.resolver.AuthRunnerPrincipal; + +import java.net.URI; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/profile/runner") +@RestController +public class RunnerCommandController { + + private final RunnerCommandService runnerCommandService; + + @PatchMapping("/me") + public ResponseEntity updateMyProfile(@AuthRunnerPrincipal final Runner runner, + @RequestBody @Valid final RunnerUpdateRequest runnerUpdateRequest) { + runnerCommandService.updateRunner(runner, runnerUpdateRequest); + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/profile/runner/me").build().toUri(); + return ResponseEntity.noContent().location(redirectUri).build(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/controller/SupporterCommandController.java b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/SupporterCommandController.java new file mode 100644 index 000000000..a9e471cbe --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/SupporterCommandController.java @@ -0,0 +1,32 @@ +package touch.baton.domain.member.command.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +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.util.UriComponentsBuilder; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.service.SupporterCommandService; +import touch.baton.domain.member.command.service.dto.SupporterUpdateRequest; +import touch.baton.domain.oauth.query.controller.resolver.AuthSupporterPrincipal; + +import java.net.URI; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/profile/supporter") +@RestController +public class SupporterCommandController { + + private final SupporterCommandService supporterCommandService; + + @PatchMapping("/me") + public ResponseEntity updateProfile(@AuthSupporterPrincipal final Supporter supporter, + @RequestBody @Valid final SupporterUpdateRequest request) { + supporterCommandService.updateSupporter(supporter, request); + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/profile/supporter/me").build().toUri(); + return ResponseEntity.noContent().location(redirectUri).build(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/controller/response/LoginMemberInfoResponse.java b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/response/LoginMemberInfoResponse.java similarity index 70% rename from backend/baton/src/main/java/touch/baton/domain/member/controller/response/LoginMemberInfoResponse.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/controller/response/LoginMemberInfoResponse.java index bf1d562bd..991c18a69 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/controller/response/LoginMemberInfoResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/controller/response/LoginMemberInfoResponse.java @@ -1,6 +1,6 @@ -package touch.baton.domain.member.controller.response; +package touch.baton.domain.member.command.controller.response; -import touch.baton.domain.member.Member; +import touch.baton.domain.member.command.Member; public record LoginMemberInfoResponse(String name, String imageUrl) { diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/repository/MemberCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/command/repository/MemberCommandRepository.java new file mode 100644 index 000000000..9345f3012 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/repository/MemberCommandRepository.java @@ -0,0 +1,7 @@ +package touch.baton.domain.member.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.member.command.Member; + +public interface MemberCommandRepository extends JpaRepository { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterCommandRepository.java new file mode 100644 index 000000000..68b71b1f7 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterCommandRepository.java @@ -0,0 +1,7 @@ +package touch.baton.domain.member.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.member.command.Supporter; + +public interface SupporterCommandRepository extends JpaRepository { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterRunnerPostCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterRunnerPostCommandRepository.java new file mode 100644 index 000000000..5400c2d9b --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/repository/SupporterRunnerPostCommandRepository.java @@ -0,0 +1,13 @@ +package touch.baton.domain.member.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.member.command.SupporterRunnerPost; + +public interface SupporterRunnerPostCommandRepository extends JpaRepository { + + boolean existsByRunnerPostId(final Long runnerPostId); + + boolean existsByRunnerPostIdAndSupporterId(final Long runnerPostId, final Long supporterId); + + void deleteBySupporterIdAndRunnerPostId(final Long supporterId, final Long runnerPostId); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/service/dto/GithubBranchManageable.java b/backend/baton/src/main/java/touch/baton/domain/member/command/service/GithubBranchManageable.java similarity index 70% rename from backend/baton/src/main/java/touch/baton/domain/member/service/dto/GithubBranchManageable.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/service/GithubBranchManageable.java index 0419a2b89..bac70d13a 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/service/dto/GithubBranchManageable.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/service/GithubBranchManageable.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.service.dto; +package touch.baton.domain.member.command.service; public interface GithubBranchManageable { diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/service/RunnerService.java b/backend/baton/src/main/java/touch/baton/domain/member/command/service/RunnerCommandService.java similarity index 57% rename from backend/baton/src/main/java/touch/baton/domain/runner/service/RunnerService.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/service/RunnerCommandService.java index 80e006be8..a8ad87d24 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/service/RunnerService.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/service/RunnerCommandService.java @@ -1,38 +1,29 @@ -package touch.baton.domain.runner.service; +package touch.baton.domain.member.command.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.common.vo.Introduction; import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.exception.RunnerBusinessException; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runner.service.dto.RunnerUpdateRequest; -import touch.baton.domain.technicaltag.RunnerTechnicalTag; -import touch.baton.domain.technicaltag.TechnicalTag; -import touch.baton.domain.technicaltag.repository.RunnerTechnicalTagRepository; -import touch.baton.domain.technicaltag.repository.TechnicalTagRepository; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.service.dto.RunnerUpdateRequest; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.Introduction; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTag; +import touch.baton.domain.technicaltag.command.TechnicalTag; +import touch.baton.domain.technicaltag.command.repository.RunnerTechnicalTagCommandRepository; +import touch.baton.domain.technicaltag.query.repository.TechnicalTagQueryRepository; import java.util.List; @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional @Service -public class RunnerService { +public class RunnerCommandService { - private final RunnerRepository runnerRepository; - private final RunnerTechnicalTagRepository runnerTechnicalTagRepository; - private final TechnicalTagRepository technicalTagRepository; + private final RunnerTechnicalTagCommandRepository runnerTechnicalTagCommandRepository; + private final TechnicalTagQueryRepository technicalTagQueryRepository; - public Runner readByRunnerId(final Long runnerId) { - return runnerRepository.joinMemberByRunnerId(runnerId) - .orElseThrow(() -> new RunnerBusinessException("Runner 가 존재하지 않습니다.")); - } - - @Transactional public void updateRunner(Runner runner, RunnerUpdateRequest runnerUpdateRequest) { runner.updateMemberName(new MemberName(runnerUpdateRequest.name())); runner.updateCompany(new Company(runnerUpdateRequest.company())); @@ -41,7 +32,7 @@ public void updateRunner(Runner runner, RunnerUpdateRequest runnerUpdateRequest) } private void updateTechnicalTags(final Runner runner, final List technicalTags) { - runnerTechnicalTagRepository.deleteByRunner(runner); + runnerTechnicalTagCommandRepository.deleteByRunner(runner); createRunnerTechnicalTags(runner, technicalTags); } @@ -57,8 +48,8 @@ private RunnerTechnicalTag createRunnerTechnicalTag(final Runner runner, final T } private TechnicalTag findTechnicalTagIfExistElseCreate(final TagName tagName) { - return technicalTagRepository.findByTagName(tagName) - .orElseGet(() -> technicalTagRepository.save( + return technicalTagQueryRepository.findByTagName(tagName) + .orElseGet(() -> technicalTagQueryRepository.save( TechnicalTag.builder() .tagName(tagName) .build()) @@ -70,6 +61,6 @@ private RunnerTechnicalTag createRunnerTechnicalTagAndSave(final Runner runner, .runner(runner) .technicalTag(technicalTag) .build(); - return runnerTechnicalTagRepository.save(runnerTechnicalTag); + return runnerTechnicalTagCommandRepository.save(runnerTechnicalTag); } } diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/service/SupporterCommandService.java b/backend/baton/src/main/java/touch/baton/domain/member/command/service/SupporterCommandService.java new file mode 100644 index 000000000..f956839ed --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/service/SupporterCommandService.java @@ -0,0 +1,53 @@ +package touch.baton.domain.member.command.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.service.dto.SupporterUpdateRequest; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.Introduction; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.TechnicalTag; +import touch.baton.domain.technicaltag.command.repository.SupporterTechnicalTagCommandRepository; +import touch.baton.domain.technicaltag.query.repository.TechnicalTagQueryRepository; + +import java.util.Optional; + +@RequiredArgsConstructor +@Transactional +@Service +public class SupporterCommandService { + + private final TechnicalTagQueryRepository technicalTagQueryRepository; + private final SupporterTechnicalTagCommandRepository supporterTechnicalTagCommandRepository; + + public void updateSupporter(final Supporter supporter, final SupporterUpdateRequest supporterUpdateRequest) { + supporter.updateMemberName(new MemberName(supporterUpdateRequest.name())); + supporter.updateCompany(new Company(supporterUpdateRequest.company())); + supporter.updateIntroduction(new Introduction(supporterUpdateRequest.introduction())); + supporterTechnicalTagCommandRepository.deleteBySupporter(supporter); + supporterUpdateRequest.technicalTags() + .forEach(tagName -> createSupporterTechnicalTag(supporter, new TagName(tagName))); + } + + private SupporterTechnicalTag createSupporterTechnicalTag(final Supporter supporter, final TagName tagName) { + final TechnicalTag technicalTag = findTechnicalTagIfExistElseCreate(tagName); + return supporterTechnicalTagCommandRepository.save(SupporterTechnicalTag.builder() + .supporter(supporter) + .technicalTag(technicalTag) + .build() + ); + } + + private TechnicalTag findTechnicalTagIfExistElseCreate(final TagName tagName) { + final Optional maybeTechnicalTag = technicalTagQueryRepository.findByTagName(tagName); + return maybeTechnicalTag.orElseGet(() -> + technicalTagQueryRepository.save(TechnicalTag.builder() + .tagName(tagName) + .build() + )); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/service/dto/GithubRepoNameRequest.java b/backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/GithubRepoNameRequest.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/member/service/dto/GithubRepoNameRequest.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/GithubRepoNameRequest.java index 11cb1aab6..1ffb313df 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/service/dto/GithubRepoNameRequest.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/GithubRepoNameRequest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.service.dto; +package touch.baton.domain.member.command.service.dto; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.validator.ValidNotNull; diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/service/dto/RunnerUpdateRequest.java b/backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/RunnerUpdateRequest.java similarity index 52% rename from backend/baton/src/main/java/touch/baton/domain/runner/service/dto/RunnerUpdateRequest.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/RunnerUpdateRequest.java index 5291ddebe..702223833 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/service/dto/RunnerUpdateRequest.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/RunnerUpdateRequest.java @@ -1,21 +1,17 @@ -package touch.baton.domain.runner.service.dto; +package touch.baton.domain.member.command.service.dto; +import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.validator.ValidNotNull; import java.util.List; -import static touch.baton.domain.common.exception.ClientErrorCode.COMPANY_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.NAME_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.RUNNER_INTRODUCTION_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.RUNNER_TECHNICAL_TAGS_ARE_NULL; - -public record RunnerUpdateRequest(@ValidNotNull(clientErrorCode = NAME_IS_NULL) +public record RunnerUpdateRequest(@ValidNotNull(clientErrorCode = ClientErrorCode.NAME_IS_NULL) String name, - @ValidNotNull(clientErrorCode = COMPANY_IS_NULL) + @ValidNotNull(clientErrorCode = ClientErrorCode.COMPANY_IS_NULL) String company, - @ValidNotNull(clientErrorCode = RUNNER_INTRODUCTION_IS_NULL) + @ValidNotNull(clientErrorCode = ClientErrorCode.RUNNER_INTRODUCTION_IS_NULL) String introduction, - @ValidNotNull(clientErrorCode = RUNNER_TECHNICAL_TAGS_ARE_NULL) + @ValidNotNull(clientErrorCode = ClientErrorCode.RUNNER_TECHNICAL_TAGS_ARE_NULL) List technicalTags ) { } diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/service/dto/SupporterUpdateRequest.java b/backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/SupporterUpdateRequest.java similarity index 68% rename from backend/baton/src/main/java/touch/baton/domain/supporter/service/dto/SupporterUpdateRequest.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/SupporterUpdateRequest.java index 2161339fc..39007afa7 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/service/dto/SupporterUpdateRequest.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/service/dto/SupporterUpdateRequest.java @@ -1,13 +1,10 @@ -package touch.baton.domain.supporter.service.dto; +package touch.baton.domain.member.command.service.dto; import touch.baton.domain.common.exception.validator.ValidNotNull; import java.util.List; -import static touch.baton.domain.common.exception.ClientErrorCode.COMPANY_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.NAME_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.SUPPORTER_INTRODUCTION_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.SUPPORTER_TECHNICAL_TAGS_ARE_NULL; +import static touch.baton.domain.common.exception.ClientErrorCode.*; public record SupporterUpdateRequest(@ValidNotNull(clientErrorCode = NAME_IS_NULL) String name, diff --git a/backend/baton/src/main/java/touch/baton/domain/member/vo/Company.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/Company.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/member/vo/Company.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/Company.java index 072a9b2af..b63ff3c56 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/vo/Company.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/Company.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/vo/GithubUrl.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/GithubUrl.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/member/vo/GithubUrl.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/GithubUrl.java index 93cc335e9..cef2fa883 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/vo/GithubUrl.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/GithubUrl.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/vo/ImageUrl.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ImageUrl.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/member/vo/ImageUrl.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/ImageUrl.java index def08fc80..e389e4ac0 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/vo/ImageUrl.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ImageUrl.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/common/vo/Introduction.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/Introduction.java similarity index 90% rename from backend/baton/src/main/java/touch/baton/domain/common/vo/Introduction.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/Introduction.java index dfd2363e6..5bdcf7a31 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/vo/Introduction.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/Introduction.java @@ -1,11 +1,10 @@ -package touch.baton.domain.common.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import touch.baton.domain.common.exception.DomainException; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/vo/MemberName.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/MemberName.java similarity index 93% rename from backend/baton/src/main/java/touch/baton/domain/member/vo/MemberName.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/MemberName.java index 2c900d3f8..6b7e21786 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/vo/MemberName.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/MemberName.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/vo/Message.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/Message.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/supporter/vo/Message.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/Message.java index 3228062cb..cfa16391b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/vo/Message.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/Message.java @@ -1,4 +1,4 @@ -package touch.baton.domain.supporter.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/vo/OauthId.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/OauthId.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/member/vo/OauthId.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/OauthId.java index 0b944f8f0..88437c360 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/vo/OauthId.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/OauthId.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/vo/ReviewCount.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ReviewCount.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/supporter/vo/ReviewCount.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/ReviewCount.java index fe75db747..958b89132 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/vo/ReviewCount.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ReviewCount.java @@ -1,4 +1,4 @@ -package touch.baton.domain.supporter.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/vo/SocialId.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/SocialId.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/member/vo/SocialId.java rename to backend/baton/src/main/java/touch/baton/domain/member/command/vo/SocialId.java index f4ce90f3d..aae0a1080 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/vo/SocialId.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/SocialId.java @@ -1,4 +1,4 @@ -package touch.baton.domain.member.vo; +package touch.baton.domain.member.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerBusinessException.java b/backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerBusinessException.java similarity index 83% rename from backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerBusinessException.java rename to backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerBusinessException.java index 0662d1c16..8b8be4303 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerBusinessException.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerBusinessException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runner.exception; +package touch.baton.domain.member.exception; import touch.baton.domain.common.exception.BusinessException; diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerDomainException.java b/backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerDomainException.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerDomainException.java rename to backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerDomainException.java index 785f80016..6d6796b05 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerDomainException.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerDomainException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runner.exception; +package touch.baton.domain.member.exception; import touch.baton.domain.common.exception.DomainException; diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerRequestException.java b/backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerRequestException.java similarity index 87% rename from backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerRequestException.java rename to backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerRequestException.java index a259334ca..1e2528ae7 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/exception/RunnerRequestException.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/exception/RunnerRequestException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runner.exception; +package touch.baton.domain.member.exception; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.ClientRequestException; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterBusinessException.java b/backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterBusinessException.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterBusinessException.java rename to backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterBusinessException.java index 3eb88cabd..77adddec2 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterBusinessException.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterBusinessException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.supporter.exception; +package touch.baton.domain.member.exception; import touch.baton.domain.common.exception.BusinessException; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterDomainException.java b/backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterDomainException.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterDomainException.java rename to backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterDomainException.java index 0c94c5d8d..ecde844ef 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterDomainException.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterDomainException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.supporter.exception; +package touch.baton.domain.member.exception; import touch.baton.domain.common.exception.DomainException; diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterRequestException.java b/backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterRequestException.java similarity index 86% rename from backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterRequestException.java rename to backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterRequestException.java index 67b19cec9..f1d3b55a5 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/exception/SupporterRequestException.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/exception/SupporterRequestException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.supporter.exception; +package touch.baton.domain.member.exception; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.ClientRequestException; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/controller/MemberProfileController.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/MemberQueryController.java similarity index 64% rename from backend/baton/src/main/java/touch/baton/domain/member/controller/MemberProfileController.java rename to backend/baton/src/main/java/touch/baton/domain/member/query/controller/MemberQueryController.java index 89d85e6c4..20f6dbd0a 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/controller/MemberProfileController.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/MemberQueryController.java @@ -1,18 +1,18 @@ -package touch.baton.domain.member.controller; +package touch.baton.domain.member.query.controller; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.controller.response.LoginMemberInfoResponse; -import touch.baton.domain.oauth.controller.resolver.AuthMemberPrincipal; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.controller.response.LoginMemberInfoResponse; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipal; @RequiredArgsConstructor @RequestMapping("/api/v1/profile") @RestController -public class MemberProfileController { +public class MemberQueryController { @GetMapping("/me") ResponseEntity readLoginMemberInfo(@AuthMemberPrincipal final Member member) { diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/controller/RunnerQueryController.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/RunnerQueryController.java new file mode 100644 index 000000000..073670b57 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/RunnerQueryController.java @@ -0,0 +1,33 @@ +package touch.baton.domain.member.query.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.query.controller.response.RunnerResponse; +import touch.baton.domain.member.query.service.RunnerQueryService; +import touch.baton.domain.oauth.query.controller.resolver.AuthRunnerPrincipal; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/profile/runner") +@RestController +public class RunnerQueryController { + + private final RunnerQueryService runnerQueryService; + + @GetMapping("/me") + public ResponseEntity readMyProfileByToken(@AuthRunnerPrincipal Runner runner) { + final RunnerResponse.Mine response = RunnerResponse.Mine.from(runner); + return ResponseEntity.ok(response); + } + + @GetMapping("/{runnerId}") + public ResponseEntity readRunnerProfile(@PathVariable Long runnerId) { + final Runner runner = runnerQueryService.readByRunnerId(runnerId); + final RunnerResponse.Detail response = RunnerResponse.Detail.from(runner); + return ResponseEntity.ok(response); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/controller/SupporterQueryController.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/SupporterQueryController.java new file mode 100644 index 000000000..accb59836 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/SupporterQueryController.java @@ -0,0 +1,35 @@ +package touch.baton.domain.member.query.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.query.controller.response.SupporterResponse; +import touch.baton.domain.member.query.service.SupporterQueryService; +import touch.baton.domain.oauth.query.controller.resolver.AuthSupporterPrincipal; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/profile/supporter") +@RestController +public class SupporterQueryController { + + private final SupporterQueryService supporterQueryService; + + @GetMapping("/{supporterId}") + public ResponseEntity readProfileBySupporterId(@PathVariable final Long supporterId) { + final Supporter foundSupporter = supporterQueryService.readBySupporterId(supporterId); + final SupporterResponse.Profile response = SupporterResponse.Profile.from(foundSupporter); + + return ResponseEntity.ok(response); + } + + @GetMapping("/me") + public ResponseEntity readSupporterMyProfileByLoginToken(@AuthSupporterPrincipal final Supporter loginedSupporter) { + final SupporterResponse.MyProfile response = SupporterResponse.MyProfile.from(loginedSupporter); + + return ResponseEntity.ok(response); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerResponse.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/RunnerResponse.java similarity index 55% rename from backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerResponse.java rename to backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/RunnerResponse.java index 0c8bf2696..2728cfda8 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/RunnerResponse.java @@ -1,6 +1,7 @@ -package touch.baton.domain.runner.controller.response; +package touch.baton.domain.member.query.controller.response; -import touch.baton.domain.runner.Runner; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; import java.util.List; @@ -8,12 +9,37 @@ public record RunnerResponse() { public record Detail(Long runnerId, String name, + String imageUrl, + String githubUrl, + String introduction, String company, - String imageUrl + List technicalTags ) { public static Detail from(final Runner runner) { - return new Detail( + final List tagNames = runner.getRunnerTechnicalTags().getRunnerTechnicalTags().stream() + .map(runnerTechnicalTag -> runnerTechnicalTag.getTechnicalTag().getTagName().getValue()) + .toList(); + + final Member member = runner.getMember(); + return new Detail(runner.getId(), + member.getMemberName().getValue(), + member.getImageUrl().getValue(), + member.getGithubUrl().getValue(), + runner.getIntroduction().getValue(), + member.getCompany().getValue(), + tagNames); + } + } + + public record InRunnerPostDetail(Long runnerId, + String name, + String company, + String imageUrl + ) { + + public static InRunnerPostDetail from(final Runner runner) { + return new InRunnerPostDetail( runner.getId(), runner.getMember().getMemberName().getValue(), runner.getMember().getCompany().getValue(), @@ -33,31 +59,15 @@ public static Simple from(final Runner runner) { } public record Mine(String name, + String company, String imageUrl, String githubUrl, - String introduction + String introduction, + List technicalTags ) { public static Mine from(final Runner runner) { return new Mine( - runner.getMember().getMemberName().getValue(), - runner.getMember().getImageUrl().getValue(), - runner.getMember().getGithubUrl().getValue(), - runner.getIntroduction().getValue() - ); - } - } - - public record MyProfile(String name, - String company, - String imageUrl, - String githubUrl, - String introduction, - List technicalTags - ) { - - public static MyProfile from(final Runner runner) { - return new MyProfile( runner.getMember().getMemberName().getValue(), runner.getMember().getCompany().getValue(), runner.getMember().getImageUrl().getValue(), diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/response/SupporterResponse.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/SupporterResponse.java similarity index 96% rename from backend/baton/src/main/java/touch/baton/domain/supporter/controller/response/SupporterResponse.java rename to backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/SupporterResponse.java index 69d9d891a..ed9780e49 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/response/SupporterResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/SupporterResponse.java @@ -1,6 +1,6 @@ -package touch.baton.domain.supporter.controller.response; +package touch.baton.domain.member.query.controller.response; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Supporter; import java.util.List; diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/repository/RunnerRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RunnerQueryRepository.java similarity index 71% rename from backend/baton/src/main/java/touch/baton/domain/runner/repository/RunnerRepository.java rename to backend/baton/src/main/java/touch/baton/domain/member/query/repository/RunnerQueryRepository.java index c5e8998b8..3828a1a9e 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runner/repository/RunnerRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RunnerQueryRepository.java @@ -1,13 +1,13 @@ -package touch.baton.domain.runner.repository; +package touch.baton.domain.member.query.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.runner.Runner; +import touch.baton.domain.member.command.Runner; import java.util.Optional; -public interface RunnerRepository extends JpaRepository { +public interface RunnerQueryRepository extends JpaRepository { @Query(""" select r diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/SupporterQueryRepository.java similarity index 71% rename from backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRepository.java rename to backend/baton/src/main/java/touch/baton/domain/member/query/repository/SupporterQueryRepository.java index da9865b8e..90e152206 100644 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/SupporterQueryRepository.java @@ -1,13 +1,13 @@ -package touch.baton.domain.supporter.repository; +package touch.baton.domain.member.query.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Supporter; import java.util.Optional; -public interface SupporterRepository extends JpaRepository { +public interface SupporterQueryRepository extends JpaRepository { @Query(""" select s diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/repository/SupporterRunnerPostQueryRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/SupporterRunnerPostQueryRepository.java new file mode 100644 index 000000000..171f19605 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/SupporterRunnerPostQueryRepository.java @@ -0,0 +1,39 @@ +package touch.baton.domain.member.query.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import touch.baton.domain.member.command.SupporterRunnerPost; + +import java.util.List; +import java.util.Optional; + +public interface SupporterRunnerPostQueryRepository extends JpaRepository { + + @Query(""" + select count(1) + from SupporterRunnerPost srp + group by srp.runnerPost.id + having srp.runnerPost.id = :runnerPostId + """) + Optional countByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); + + @Query(""" + select (count(1) >= 1) + from SupporterRunnerPost srp + join fetch Member m on m.id = srp.supporter.member.id + where srp.runnerPost.id = :runnerPostId + and srp.supporter.member.id = :memberId + """) + boolean existsByRunnerPostIdAndMemberId(@Param("runnerPostId") final Long runnerPostId, @Param("memberId") final Long memberId); + + List readByRunnerPostId(final Long runnerPostId); + + @Query(""" + select count(1) + from SupporterRunnerPost srp + join fetch RunnerPost rp on rp.id = srp.runnerPost.id + where rp.reviewStatus = 'NOT_STARTED' and srp.supporter.id = :supporterId + """) + long countRunnerPostBySupporterIdByReviewStatusNotStarted(@Param("supporterId") final Long supporterId); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/service/RunnerQueryService.java b/backend/baton/src/main/java/touch/baton/domain/member/query/service/RunnerQueryService.java new file mode 100644 index 000000000..6697bfc49 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/service/RunnerQueryService.java @@ -0,0 +1,21 @@ +package touch.baton.domain.member.query.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.exception.RunnerBusinessException; +import touch.baton.domain.member.query.repository.RunnerQueryRepository; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class RunnerQueryService { + + private final RunnerQueryRepository runnerQueryRepository; + + public Runner readByRunnerId(final Long runnerId) { + return runnerQueryRepository.joinMemberByRunnerId(runnerId) + .orElseThrow(() -> new RunnerBusinessException("Runner 가 존재하지 않습니다.")); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/service/SupporterQueryService.java b/backend/baton/src/main/java/touch/baton/domain/member/query/service/SupporterQueryService.java new file mode 100644 index 000000000..7345f150e --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/service/SupporterQueryService.java @@ -0,0 +1,21 @@ +package touch.baton.domain.member.query.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.exception.SupporterBusinessException; +import touch.baton.domain.member.query.repository.SupporterQueryRepository; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class SupporterQueryService { + + private final SupporterQueryRepository supporterQueryRepository; + + public Supporter readBySupporterId(final Long supporterId) { + return supporterQueryRepository.joinMemberBySupporterId(supporterId) + .orElseThrow(() -> new SupporterBusinessException("존재하지 않는 서포터 식별자값으로 조회할 수 없습니다.")); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/repository/MemberRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/repository/MemberRepository.java deleted file mode 100644 index 83f47b92d..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/member/repository/MemberRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package touch.baton.domain.member.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import touch.baton.domain.member.Member; - -public interface MemberRepository extends JpaRepository { -} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/Notification.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/Notification.java new file mode 100644 index 000000000..3528cad21 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/Notification.java @@ -0,0 +1,146 @@ +package touch.baton.domain.notification.command; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; +import touch.baton.domain.common.TruncatedBaseEntity; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.vo.IsRead; +import touch.baton.domain.notification.command.vo.NotificationMessage; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.notification.command.vo.NotificationTitle; +import touch.baton.domain.notification.command.vo.NotificationType; +import touch.baton.domain.notification.exception.NotificationDomainException; + +import java.util.Objects; + +import static jakarta.persistence.EnumType.STRING; +import static jakarta.persistence.FetchType.LAZY; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +@Getter +@NoArgsConstructor(access = PROTECTED) +@Where(clause = "deleted_at IS NULL") +@SQLDelete(sql = "UPDATE notification SET deleted_at = now() WHERE id = ?") +@Entity +public class Notification extends TruncatedBaseEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + + @Embedded + private NotificationTitle notificationTitle; + + @Embedded + private NotificationMessage notificationMessage; + + @Enumerated(STRING) + @Column(nullable = false) + private NotificationType notificationType; + + @Embedded + private NotificationReferencedId notificationReferencedId; + + @Embedded + private IsRead isRead; + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "member_id", + nullable = false, + foreignKey = @ForeignKey(name = "fk_notification_to_member")) + private Member member; + + @Builder + private Notification(final NotificationTitle notificationTitle, + final NotificationMessage notificationMessage, + final NotificationType notificationType, + final NotificationReferencedId notificationReferencedId, + final IsRead isRead, + final Member member + ) { + this(null, notificationTitle, notificationMessage, notificationType, notificationReferencedId, isRead, member); + } + + private Notification(final Long id, + final NotificationTitle notificationTitle, + final NotificationMessage notificationMessage, + final NotificationType notificationType, + final NotificationReferencedId notificationReferencedId, + final IsRead isRead, + final Member member + ) { + validateNotNull(notificationTitle, notificationMessage, notificationType, notificationReferencedId, isRead, member); + this.id = id; + this.notificationTitle = notificationTitle; + this.notificationMessage = notificationMessage; + this.notificationType = notificationType; + this.notificationReferencedId = notificationReferencedId; + this.isRead = isRead; + this.member = member; + } + + private void validateNotNull(final NotificationTitle notificationTitle, + final NotificationMessage notificationMessage, + final NotificationType notificationType, + final NotificationReferencedId notificationReferencedId, + final IsRead isRead, + final Member member + ) { + if (notificationTitle == null) { + throw new NotificationDomainException("NotificationTitle 의 notificationTitle 은 null 일 수 없습니다."); + } + if (notificationMessage == null) { + throw new NotificationDomainException("NotificationMessage 의 notificationMessage 는 null 일 수 없습니다."); + } + if (notificationType == null) { + throw new NotificationDomainException("NotificationType 의 notificationType 는 null 일 수 없습니다."); + } + if (notificationReferencedId == null) { + throw new NotificationDomainException("NotificationReferencedId 의 notificationReferencedId 은 null 일 수 없습니다."); + } + if (isRead == null) { + throw new NotificationDomainException("IsRead 의 isRead 는 null 일 수 없습니다."); + } + if (member == null) { + throw new NotificationDomainException("Member 의 member 는 null 일 수 없습니다."); + } + } + + public void markAsRead(final Member currentMember) { + if (!this.member.equals(currentMember)) { + throw new NotificationDomainException("Notification 의 주인(사용자)가 아니므로 알림의 읽은 여부를 수정할 수 없습니다."); + } + + this.isRead = IsRead.asRead(); + } + + public boolean isNotOwner(final Member currentMember) { + return !this.member.equals(currentMember); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Notification notification = (Notification) o; + return Objects.equals(id, notification.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/controller/NotificationCommandController.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/controller/NotificationCommandController.java new file mode 100644 index 000000000..488cfc99d --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/controller/NotificationCommandController.java @@ -0,0 +1,38 @@ +package touch.baton.domain.notification.command.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import touch.baton.domain.notification.command.service.NotificationCommandService; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipal; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/notifications") +@RestController +public class NotificationCommandController { + + private final NotificationCommandService notificationCommandService; + + @PatchMapping("/{notificationId}") + public ResponseEntity updateNotificationIsReadTrueByNotificationId(@AuthMemberPrincipal final Member member, + @PathVariable final Long notificationId + ) { + notificationCommandService.updateNotificationIsReadTrueByMember(member, notificationId); + + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/{notificationId}") + public ResponseEntity deleteNotificationByNotificationId(@AuthMemberPrincipal final Member member, + @PathVariable final Long notificationId + ) { + notificationCommandService.deleteNotificationByMember(member, notificationId); + + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/event/NotificationEventListener.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/event/NotificationEventListener.java new file mode 100644 index 000000000..e7d27e42f --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/event/NotificationEventListener.java @@ -0,0 +1,82 @@ +package touch.baton.domain.notification.command.event; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.repository.NotificationCommandRepository; +import touch.baton.domain.notification.command.vo.IsRead; +import touch.baton.domain.notification.command.vo.NotificationMessage; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.notification.command.vo.NotificationTitle; +import touch.baton.domain.notification.command.vo.NotificationType; +import touch.baton.domain.notification.exception.NotificationBusinessException; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.event.RunnerPostApplySupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostAssignSupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostReviewStatusDoneEvent; +import touch.baton.domain.runnerpost.query.repository.RunnerPostQueryRepository; + +import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; +import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT; + +@RequiredArgsConstructor +@Component +public class NotificationEventListener { + + private final NotificationCommandRepository notificationCommandRepository; + private final RunnerPostQueryRepository runnerPostQueryRepository; + + @TransactionalEventListener(phase = AFTER_COMMIT) + @Transactional(propagation = REQUIRES_NEW) + public void subscribeRunnerPostApplySupporterEvent(final RunnerPostApplySupporterEvent event) { + final RunnerPost foundRunnerPost = getRunnerPostWithRunnerOrThrowException(event.runnerPostId()); + + notificationCommandRepository.save( + createNotification("서포터의 제안이 왔습니다.", foundRunnerPost, foundRunnerPost.getRunner().getMember()) + ); + } + + private RunnerPost getRunnerPostWithRunnerOrThrowException(final Long runnerPostId) { + return runnerPostQueryRepository.joinMemberByRunnerPostId(runnerPostId) + .orElseThrow(() -> new NotificationBusinessException("러너 게시글 식별자값으로 러너 게시글과 러너(작성자)를 조회하던 도중에 오류가 발생하였습니다.")); + } + + private Notification createNotification(final String notificationTitle, final RunnerPost runnerPost, final Member targetMember) { + return Notification.builder() + .notificationTitle(new NotificationTitle(notificationTitle)) + .notificationMessage(new NotificationMessage(String.format("관련 게시글 - %s", runnerPost.getTitle().getValue()))) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(runnerPost.getId())) + .isRead(IsRead.asUnRead()) + .member(targetMember) + .build(); + } + + @TransactionalEventListener(phase = AFTER_COMMIT) + @Transactional(propagation = REQUIRES_NEW) + public void subscribeRunnerPostReviewStatusDoneEvent(final RunnerPostReviewStatusDoneEvent event) { + final RunnerPost foundRunnerPost = getRunnerPostWithRunnerOrThrowException(event.runnerPostId()); + + notificationCommandRepository.save( + createNotification("코드 리뷰 상태가 완료로 변경되었습니다.", foundRunnerPost, foundRunnerPost.getRunner().getMember()) + ); + } + + @TransactionalEventListener(phase = AFTER_COMMIT) + @Transactional(propagation = REQUIRES_NEW) + public void subscribeRunnerPostAssignSupporterEvent(final RunnerPostAssignSupporterEvent event) { + final RunnerPost foundRunnerPost = getRunnerPostWithSupporterOrThrowException(event.runnerPostId()); + + notificationCommandRepository.save( + createNotification("코드 리뷰 매칭이 완료되었습니다.", foundRunnerPost, foundRunnerPost.getSupporter().getMember()) + ); + } + + private RunnerPost getRunnerPostWithSupporterOrThrowException(final Long runnerPostId) { + return runnerPostQueryRepository.joinSupporterByRunnerPostId(runnerPostId) + .orElseThrow(() -> new NotificationBusinessException("러너 게시글 식별자값으로 러너 게시글과 서포터(지원자)를 조회하던 도중에 오류가 발생하였습니다.")); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/repository/NotificationCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/repository/NotificationCommandRepository.java new file mode 100644 index 000000000..f0b32bc6e --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/repository/NotificationCommandRepository.java @@ -0,0 +1,7 @@ +package touch.baton.domain.notification.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.notification.command.Notification; + +public interface NotificationCommandRepository extends JpaRepository { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/service/NotificationCommandService.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/service/NotificationCommandService.java new file mode 100644 index 000000000..c3551428c --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/service/NotificationCommandService.java @@ -0,0 +1,35 @@ +package touch.baton.domain.notification.command.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.repository.NotificationCommandRepository; +import touch.baton.domain.notification.exception.NotificationBusinessException; +import touch.baton.domain.member.command.Member; + +@RequiredArgsConstructor +@Transactional +@Service +public class NotificationCommandService { + + private final NotificationCommandRepository notificationCommandRepository; + + public void updateNotificationIsReadTrueByMember(final Member member, final Long notificationId) { + final Notification foundNotification = getNotificationByNotificationId(notificationId); + foundNotification.markAsRead(member); + } + + private Notification getNotificationByNotificationId(final Long notificationId) { + return notificationCommandRepository.findById(notificationId) + .orElseThrow(() -> new NotificationBusinessException("Notification 식별자값으로 알림을 조회할 수 없습니다.")); + } + + public void deleteNotificationByMember(final Member member, final Long notificationId) { + if (getNotificationByNotificationId(notificationId).isNotOwner(member)) { + throw new NotificationBusinessException("Notification 의 주인(사용자)가 아니므로 알림을 삭제할 수 없습니다."); + } + + notificationCommandRepository.deleteById(notificationId); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/IsRead.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/IsRead.java new file mode 100644 index 000000000..20fe24a13 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/IsRead.java @@ -0,0 +1,35 @@ +package touch.baton.domain.notification.command.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.ColumnDefault; + +import static lombok.AccessLevel.PROTECTED; + +@EqualsAndHashCode +@NoArgsConstructor(access = PROTECTED) +@Embeddable +public class IsRead { + + @ColumnDefault(value = "false") + @Column(name = "is_read", nullable = false) + private boolean value = false; + + private IsRead(final boolean value) { + this.value = value; + } + + public static IsRead asRead() { + return new IsRead(true); + } + + public static IsRead asUnRead() { + return new IsRead(false); + } + + public boolean getValue() { + return value; + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationMessage.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationMessage.java new file mode 100644 index 000000000..6181bf570 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationMessage.java @@ -0,0 +1,32 @@ +package touch.baton.domain.notification.command.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +import static lombok.AccessLevel.PROTECTED; + +@EqualsAndHashCode +@Getter +@NoArgsConstructor(access = PROTECTED) +@Embeddable +public class NotificationMessage { + + @Column(name = "message", nullable = false) + private String value; + + public NotificationMessage(final String value) { + validateNotNull(value); + this.value = value; + } + + private void validateNotNull(final String value) { + if (Objects.isNull(value)) { + throw new IllegalArgumentException("NotificationMessage 객체 내부에 message 는 null 일 수 없습니다."); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationReferencedId.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationReferencedId.java new file mode 100644 index 000000000..1892a01fe --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationReferencedId.java @@ -0,0 +1,30 @@ +package touch.baton.domain.notification.command.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import static lombok.AccessLevel.PROTECTED; + +@EqualsAndHashCode +@Getter +@NoArgsConstructor(access = PROTECTED) +@Embeddable +public class NotificationReferencedId { + + @Column(name = "referenced_id", nullable = false) + private Long value; + + public NotificationReferencedId(final Long value) { + validateNotNull(value); + this.value = value; + } + + private void validateNotNull(final Long value) { + if (value == null) { + throw new IllegalArgumentException("NotificationReferencedId 객체 내부에 referencedId 은 null 일 수 없습니다."); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationTitle.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationTitle.java new file mode 100644 index 000000000..b49bd80a6 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationTitle.java @@ -0,0 +1,32 @@ +package touch.baton.domain.notification.command.vo; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Objects; + +import static lombok.AccessLevel.PROTECTED; + +@EqualsAndHashCode +@Getter +@NoArgsConstructor(access = PROTECTED) +@Embeddable +public class NotificationTitle { + + @Column(name = "title", nullable = false) + private String value; + + public NotificationTitle(final String value) { + validateNotNull(value); + this.value = value; + } + + private void validateNotNull(final String value) { + if (Objects.isNull(value)) { + throw new IllegalArgumentException("NotificationTitle 객체 내부에 title 은 null 일 수 없습니다."); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationType.java b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationType.java new file mode 100644 index 000000000..14a768acb --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/command/vo/NotificationType.java @@ -0,0 +1,6 @@ +package touch.baton.domain.notification.command.vo; + +public enum NotificationType { + + RUNNER_POST; +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationBusinessException.java b/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationBusinessException.java new file mode 100644 index 000000000..3e9a6af08 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationBusinessException.java @@ -0,0 +1,10 @@ +package touch.baton.domain.notification.exception; + +import touch.baton.domain.common.exception.BusinessException; + +public class NotificationBusinessException extends BusinessException { + + public NotificationBusinessException(final String message) { + super(message); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationDomainException.java b/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationDomainException.java new file mode 100644 index 000000000..18d66ce9b --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationDomainException.java @@ -0,0 +1,10 @@ +package touch.baton.domain.notification.exception; + +import touch.baton.domain.common.exception.DomainException; + +public class NotificationDomainException extends DomainException { + + public NotificationDomainException(final String message) { + super(message); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationRequestException.java b/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationRequestException.java new file mode 100644 index 000000000..16ac4d398 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/exception/NotificationRequestException.java @@ -0,0 +1,11 @@ +package touch.baton.domain.notification.exception; + +import touch.baton.domain.common.exception.ClientErrorCode; +import touch.baton.domain.common.exception.ClientRequestException; + +public class NotificationRequestException extends ClientRequestException { + + public NotificationRequestException(final ClientErrorCode errorCode) { + super(errorCode); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/NotificationQueryController.java b/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/NotificationQueryController.java new file mode 100644 index 000000000..fee64a9f7 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/NotificationQueryController.java @@ -0,0 +1,31 @@ +package touch.baton.domain.notification.query.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.query.controller.response.NotificationResponses; +import touch.baton.domain.notification.query.service.NotificationQueryService; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipal; + +import java.util.List; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/notifications") +@RestController +public class NotificationQueryController { + + private static final int READ_NOTIFICATION_DEFAULT_LIMIT = 10; + + private final NotificationQueryService notificationQueryService; + + @GetMapping + public ResponseEntity readNotificationsByMember(@AuthMemberPrincipal final Member member) { + final List foundNotifications = notificationQueryService.readNotificationsByMemberId(member.getId(), READ_NOTIFICATION_DEFAULT_LIMIT); + + return ResponseEntity.ok(NotificationResponses.SimpleNotifications.from(foundNotifications)); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponse.java b/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponse.java new file mode 100644 index 000000000..ebd6f789a --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponse.java @@ -0,0 +1,31 @@ +package touch.baton.domain.notification.query.controller.response; + +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.NotificationType; + +import java.time.LocalDateTime; + +public record NotificationResponse() { + + public record Simple(Long notificationId, + String title, + String message, + NotificationType notificationType, + Long referencedId, + boolean isRead, + LocalDateTime createdAt + ) { + + public static Simple from(final Notification notification) { + return new NotificationResponse.Simple( + notification.getId(), + notification.getNotificationTitle().getValue(), + notification.getNotificationMessage().getValue(), + notification.getNotificationType(), + notification.getNotificationReferencedId().getValue(), + notification.getIsRead().getValue(), + notification.getCreatedAt() + ); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponses.java b/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponses.java new file mode 100644 index 000000000..d830165ef --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/query/controller/response/NotificationResponses.java @@ -0,0 +1,19 @@ +package touch.baton.domain.notification.query.controller.response; + +import touch.baton.domain.notification.command.Notification; + +import java.util.List; + +public record NotificationResponses() { + + public record SimpleNotifications(List data) { + + public static SimpleNotifications from(final List notifications) { + final List response = notifications.stream() + .map(NotificationResponse.Simple::from) + .toList(); + + return new SimpleNotifications(response); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepository.java b/backend/baton/src/main/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepository.java new file mode 100644 index 000000000..d723ac8cf --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepository.java @@ -0,0 +1,27 @@ +package touch.baton.domain.notification.query.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import touch.baton.domain.notification.command.Notification; + +import java.util.List; + +import static touch.baton.domain.notification.command.QNotification.notification; + +@RequiredArgsConstructor +@Repository +public class NotificationQuerydslRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findByMemberId(final Long memberId, + final int limit + ) { + return jpaQueryFactory.selectFrom(notification) + .where(notification.member.id.eq(memberId)) + .orderBy(notification.id.desc()) + .limit(limit) + .fetch(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/notification/query/service/NotificationQueryService.java b/backend/baton/src/main/java/touch/baton/domain/notification/query/service/NotificationQueryService.java new file mode 100644 index 000000000..ece728ab8 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/notification/query/service/NotificationQueryService.java @@ -0,0 +1,21 @@ +package touch.baton.domain.notification.query.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.query.repository.NotificationQuerydslRepository; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class NotificationQueryService { + + private final NotificationQuerydslRepository notificationQuerydslRepository; + + public List readNotificationsByMemberId(final Long memberId, final int limit) { + return notificationQuerydslRepository.findByMemberId(memberId, limit); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/client/OauthInformationClient.java b/backend/baton/src/main/java/touch/baton/domain/oauth/client/OauthInformationClient.java deleted file mode 100644 index 21c9a00bb..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/client/OauthInformationClient.java +++ /dev/null @@ -1,11 +0,0 @@ -package touch.baton.domain.oauth.client; - -import touch.baton.domain.oauth.OauthInformation; -import touch.baton.domain.oauth.OauthType; - -public interface OauthInformationClient { - - OauthType oauthType(); - - OauthInformation fetchInformation(final String authCode); -} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/AuthorizationHeader.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/AuthorizationHeader.java similarity index 93% rename from backend/baton/src/main/java/touch/baton/domain/oauth/AuthorizationHeader.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/AuthorizationHeader.java index 16fd4f7cd..24083a6c3 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/AuthorizationHeader.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/AuthorizationHeader.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth; +package touch.baton.domain.oauth.command; public class AuthorizationHeader { diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/OauthInformation.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/OauthInformation.java similarity index 73% rename from backend/baton/src/main/java/touch/baton/domain/oauth/OauthInformation.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/OauthInformation.java index 1288ecfb1..6f53e8740 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/OauthInformation.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/OauthInformation.java @@ -1,15 +1,15 @@ -package touch.baton.domain.oauth; +package touch.baton.domain.oauth.command; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.token.SocialToken; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.token.SocialToken; import static lombok.AccessLevel.PROTECTED; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/OauthType.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/OauthType.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/oauth/OauthType.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/OauthType.java index fb2de4143..7c7a46a5b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/OauthType.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/OauthType.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth; +package touch.baton.domain.oauth.command; import static java.util.Locale.ENGLISH; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/authcode/AuthCodeRequestUrlProvider.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/authcode/AuthCodeRequestUrlProvider.java similarity index 51% rename from backend/baton/src/main/java/touch/baton/domain/oauth/authcode/AuthCodeRequestUrlProvider.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/authcode/AuthCodeRequestUrlProvider.java index 089b64cf5..b67c059b6 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/authcode/AuthCodeRequestUrlProvider.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/authcode/AuthCodeRequestUrlProvider.java @@ -1,6 +1,6 @@ -package touch.baton.domain.oauth.authcode; +package touch.baton.domain.oauth.command.authcode; -import touch.baton.domain.oauth.OauthType; +import touch.baton.domain.oauth.command.OauthType; public interface AuthCodeRequestUrlProvider { diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/authcode/AuthCodeRequestUrlProviderComposite.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/authcode/AuthCodeRequestUrlProviderComposite.java similarity index 87% rename from backend/baton/src/main/java/touch/baton/domain/oauth/authcode/AuthCodeRequestUrlProviderComposite.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/authcode/AuthCodeRequestUrlProviderComposite.java index eba86ab74..a50630956 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/authcode/AuthCodeRequestUrlProviderComposite.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/authcode/AuthCodeRequestUrlProviderComposite.java @@ -1,10 +1,10 @@ -package touch.baton.domain.oauth.authcode; +package touch.baton.domain.oauth.command.authcode; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.exception.OauthRequestException; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.exception.OauthRequestException; import java.util.Map; import java.util.Optional; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/client/OauthInformationClient.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/client/OauthInformationClient.java new file mode 100644 index 000000000..211358276 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/client/OauthInformationClient.java @@ -0,0 +1,11 @@ +package touch.baton.domain.oauth.command.client; + +import touch.baton.domain.oauth.command.OauthInformation; +import touch.baton.domain.oauth.command.OauthType; + +public interface OauthInformationClient { + + OauthType oauthType(); + + OauthInformation fetchInformation(final String authCode); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/client/OauthInformationClientComposite.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/client/OauthInformationClientComposite.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/oauth/client/OauthInformationClientComposite.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/client/OauthInformationClientComposite.java index 0a657aa48..25d47e232 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/client/OauthInformationClientComposite.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/client/OauthInformationClientComposite.java @@ -1,11 +1,11 @@ -package touch.baton.domain.oauth.client; +package touch.baton.domain.oauth.command.client; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.oauth.OauthInformation; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.exception.OauthRequestException; +import touch.baton.domain.oauth.command.OauthInformation; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.exception.OauthRequestException; import java.util.Map; import java.util.Optional; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/OauthController.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/controller/OauthCommandController.java similarity index 75% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/OauthController.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/controller/OauthCommandController.java index 98d4383df..497888c99 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/OauthController.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/controller/OauthCommandController.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.controller; +package touch.baton.domain.oauth.command.controller; import jakarta.annotation.Nullable; import jakarta.servlet.http.HttpServletRequest; @@ -9,6 +9,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; +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.RequestMapping; @@ -16,11 +17,13 @@ import org.springframework.web.bind.annotation.RestController; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.ClientRequestException; -import touch.baton.domain.oauth.AuthorizationHeader; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.service.OauthService; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Tokens; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.AuthorizationHeader; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.service.OauthCommandService; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Tokens; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipal; import java.io.IOException; import java.time.Duration; @@ -32,15 +35,15 @@ @RequiredArgsConstructor @RequestMapping("/api/v1/oauth") @RestController -public class OauthController { +public class OauthCommandController { - private final OauthService oauthService; + private final OauthCommandService oauthCommandService; @GetMapping("/{oauthType}") public ResponseEntity redirectAuthCode(@PathVariable("oauthType") final OauthType oauthType, final HttpServletResponse response ) throws IOException { - final String redirectUrl = oauthService.readAuthCodeRedirect(oauthType); + final String redirectUrl = oauthCommandService.readAuthCodeRedirect(oauthType); response.sendRedirect(redirectUrl); return ResponseEntity.status(FOUND).build(); @@ -51,7 +54,7 @@ public ResponseEntity login(@PathVariable final OauthType oauthType, @RequestParam final String code, final HttpServletResponse response ) { - final Tokens tokens = oauthService.login(oauthType, code); + final Tokens tokens = oauthCommandService.login(oauthType, code); setCookie(response, tokens.refreshToken()); @@ -74,7 +77,7 @@ public ResponseEntity refreshJwt(@Nullable @CookieValue(required = false) final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request.getHeader(AUTHORIZATION)); - final Tokens tokens = oauthService.reissueAccessToken(authorizationHeader, refreshToken); + final Tokens tokens = oauthCommandService.reissueAccessToken(authorizationHeader, refreshToken); setCookie(response, tokens.refreshToken()); @@ -93,4 +96,11 @@ private void setCookie(final HttpServletResponse response, final RefreshToken re .build(); response.addHeader("Set-Cookie", responseCookie.toString()); } + + @PatchMapping("/logout") + public ResponseEntity logout(@AuthMemberPrincipal final Member member) { + oauthCommandService.logout(member); + + return ResponseEntity.noContent().build(); + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/exception/OauthBusinessException.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/exception/OauthBusinessException.java similarity index 80% rename from backend/baton/src/main/java/touch/baton/domain/oauth/exception/OauthBusinessException.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/exception/OauthBusinessException.java index e5a379890..386b2cd2d 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/exception/OauthBusinessException.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/exception/OauthBusinessException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.exception; +package touch.baton.domain.oauth.command.exception; import touch.baton.domain.common.exception.BusinessException; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/exception/OauthRequestException.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/exception/OauthRequestException.java similarity index 85% rename from backend/baton/src/main/java/touch/baton/domain/oauth/exception/OauthRequestException.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/exception/OauthRequestException.java index 6aa131f97..f7dd10b57 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/exception/OauthRequestException.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/exception/OauthRequestException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.exception; +package touch.baton.domain.oauth.command.exception; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.ClientRequestException; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthMemberCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthMemberCommandRepository.java new file mode 100644 index 000000000..7a1cbc420 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthMemberCommandRepository.java @@ -0,0 +1,15 @@ +package touch.baton.domain.oauth.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; + +import java.util.Optional; + +public interface OauthMemberCommandRepository extends JpaRepository { + + Optional findMemberByOauthId(final OauthId oauthId); + + Optional findBySocialId(final SocialId socialId); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthRunnerRepository.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthRunnerCommandRepository.java similarity index 66% rename from backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthRunnerRepository.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthRunnerCommandRepository.java index 0bc96920a..c25d25b64 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthRunnerRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthRunnerCommandRepository.java @@ -1,14 +1,14 @@ -package touch.baton.domain.oauth.repository; +package touch.baton.domain.oauth.command.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.SocialId; import java.util.Optional; -public interface OauthRunnerRepository extends JpaRepository { +public interface OauthRunnerCommandRepository extends JpaRepository { @Query(""" select r, r.member diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthSupporterRepository.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthSupporterCommandRepository.java similarity index 65% rename from backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthSupporterRepository.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthSupporterCommandRepository.java index 80bd43a54..de00ec030 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthSupporterRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/OauthSupporterCommandRepository.java @@ -1,14 +1,14 @@ -package touch.baton.domain.oauth.repository; +package touch.baton.domain.oauth.command.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; import java.util.Optional; -public interface OauthSupporterRepository extends JpaRepository { +public interface OauthSupporterCommandRepository extends JpaRepository { @Query(""" select s, s.member diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java new file mode 100644 index 000000000..c973ffde4 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java @@ -0,0 +1,17 @@ +package touch.baton.domain.oauth.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; + +import java.util.Optional; + +public interface RefreshTokenCommandRepository extends JpaRepository { + + Optional findByToken(final Token token); + + Optional findByMember(final Member member); + + void deleteByMember(final Member member); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/service/OauthService.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/OauthCommandService.java similarity index 66% rename from backend/baton/src/main/java/touch/baton/domain/oauth/service/OauthService.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/service/OauthCommandService.java index a3c824c10..078649e85 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/service/OauthService.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/OauthCommandService.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.service; +package touch.baton.domain.oauth.command.service; import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; @@ -6,28 +6,28 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.AuthorizationHeader; -import touch.baton.domain.oauth.OauthInformation; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.authcode.AuthCodeRequestUrlProviderComposite; -import touch.baton.domain.oauth.client.OauthInformationClientComposite; -import touch.baton.domain.oauth.exception.OauthRequestException; -import touch.baton.domain.oauth.repository.OauthMemberRepository; -import touch.baton.domain.oauth.repository.OauthRunnerRepository; -import touch.baton.domain.oauth.repository.OauthSupporterRepository; -import touch.baton.domain.oauth.repository.RefreshTokenRepository; -import touch.baton.domain.oauth.token.AccessToken; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; -import touch.baton.domain.oauth.token.Tokens; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.vo.ReviewCount; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.AuthorizationHeader; +import touch.baton.domain.oauth.command.OauthInformation; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.authcode.AuthCodeRequestUrlProviderComposite; +import touch.baton.domain.oauth.command.client.OauthInformationClientComposite; +import touch.baton.domain.oauth.command.exception.OauthRequestException; +import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; +import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; +import touch.baton.domain.oauth.command.token.AccessToken; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.domain.oauth.command.token.Tokens; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; import touch.baton.infra.auth.jwt.JwtDecoder; import touch.baton.infra.auth.jwt.JwtEncoder; @@ -38,16 +38,16 @@ import java.util.UUID; @RequiredArgsConstructor -@Transactional(readOnly = true) +@Transactional @Service -public class OauthService { +public class OauthCommandService { private final AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite; private final OauthInformationClientComposite oauthInformationClientComposite; - private final OauthMemberRepository oauthMemberRepository; - private final OauthRunnerRepository oauthRunnerRepository; - private final OauthSupporterRepository oauthSupporterRepository; - private final RefreshTokenRepository refreshTokenRepository; + private final OauthMemberCommandRepository oauthMemberCommandRepository; + private final OauthRunnerCommandRepository oauthRunnerCommandRepository; + private final OauthSupporterCommandRepository oauthSupporterCommandRepository; + private final RefreshTokenCommandRepository refreshTokenCommandRepository; private final JwtEncoder jwtEncoder; private final JwtDecoder jwtDecoder; @@ -58,11 +58,10 @@ public String readAuthCodeRedirect(final OauthType oauthType) { return authCodeRequestUrlProviderComposite.findRequestUrl(oauthType); } - @Transactional public Tokens login(final OauthType oauthType, final String code) { final OauthInformation oauthInformation = oauthInformationClientComposite.fetchInformation(oauthType, code); - final Optional maybeMember = oauthMemberRepository.findMemberByOauthId(oauthInformation.getOauthId()); + final Optional maybeMember = oauthMemberCommandRepository.findMemberByOauthId(oauthInformation.getOauthId()); if (maybeMember.isEmpty()) { final Member savedMember = signUpMember(oauthInformation); saveNewRunner(savedMember); @@ -83,7 +82,7 @@ private Member signUpMember(final OauthInformation oauthInformation) { .imageUrl(oauthInformation.getImageUrl()) .build(); - return oauthMemberRepository.save(newMember); + return oauthMemberCommandRepository.save(newMember); } private Runner saveNewRunner(final Member member) { @@ -91,7 +90,7 @@ private Runner saveNewRunner(final Member member) { .member(member) .build(); - return oauthRunnerRepository.save(newRunner); + return oauthRunnerCommandRepository.save(newRunner); } private Supporter saveNewSupporter(final Member member) { @@ -101,7 +100,7 @@ private Supporter saveNewSupporter(final Member member) { .supporterTechnicalTags(new SupporterTechnicalTags(new ArrayList<>())) .build(); - return oauthSupporterRepository.save(newSupporter); + return oauthSupporterCommandRepository.save(newSupporter); } private Tokens createTokens(final SocialId socialId, final Member member) { @@ -116,25 +115,24 @@ private Tokens createTokens(final SocialId socialId, final Member member) { .expireDate(new ExpireDate(expireDate)) .build(); - final Optional maybeRefreshToken = refreshTokenRepository.findByMember(member); + final Optional maybeRefreshToken = refreshTokenCommandRepository.findByMember(member); if (maybeRefreshToken.isPresent()) { final RefreshToken findRefreshToken = maybeRefreshToken.get(); findRefreshToken.updateToken(new Token(randomTokens), refreshTokenExpireMinutes); return new Tokens(accessToken, findRefreshToken); } - refreshTokenRepository.save(refreshToken); + refreshTokenCommandRepository.save(refreshToken); return new Tokens(accessToken, refreshToken); } - @Transactional public Tokens reissueAccessToken(final AuthorizationHeader authHeader, final String refreshToken) { final Claims claims = jwtDecoder.parseExpiredAuthorizationHeader(authHeader); final SocialId socialId = new SocialId(claims.get("socialId", String.class)); - final Member findMember = oauthMemberRepository.findBySocialId(socialId) + final Member findMember = oauthMemberCommandRepository.findBySocialId(socialId) .orElseThrow(() -> new OauthRequestException(ClientErrorCode.JWT_CLAIM_SOCIAL_ID_IS_WRONG)); - final RefreshToken findRefreshToken = refreshTokenRepository.findByToken(new Token(refreshToken)) + final RefreshToken findRefreshToken = refreshTokenCommandRepository.findByToken(new Token(refreshToken)) .orElseThrow(() -> new OauthRequestException(ClientErrorCode.REFRESH_TOKEN_IS_NOT_FOUND)); if (findRefreshToken.isNotOwner(findMember)) { @@ -161,4 +159,8 @@ private AccessToken createAccessToken(final SocialId socialId) { ); return new AccessToken(jwtToken); } + + public void logout(final Member member) { + refreshTokenCommandRepository.deleteByMember(member); + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/token/AccessToken.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/AccessToken.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/oauth/token/AccessToken.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/token/AccessToken.java index b0b5dc839..9a377b429 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/token/AccessToken.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/AccessToken.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.token; +package touch.baton.domain.oauth.command.token; import lombok.EqualsAndHashCode; import lombok.Getter; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/token/ExpireDate.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/ExpireDate.java similarity index 95% rename from backend/baton/src/main/java/touch/baton/domain/oauth/token/ExpireDate.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/token/ExpireDate.java index b20bf6af0..1efe615af 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/token/ExpireDate.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/ExpireDate.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.token; +package touch.baton.domain.oauth.command.token; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/token/RefreshToken.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/RefreshToken.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/oauth/token/RefreshToken.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/token/RefreshToken.java index 0f42590f3..ca5a9e96f 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/token/RefreshToken.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/RefreshToken.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.token; +package touch.baton.domain.oauth.command.token; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -11,8 +11,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.BaseEntity; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.token.exception.RefreshTokenDomainException; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.token.exception.RefreshTokenDomainException; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/token/SocialToken.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/SocialToken.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/oauth/token/SocialToken.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/token/SocialToken.java index f9e60cf03..2bd9e237b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/token/SocialToken.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/SocialToken.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.token; +package touch.baton.domain.oauth.command.token; import lombok.EqualsAndHashCode; import lombok.Getter; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/token/Token.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Token.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/oauth/token/Token.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Token.java index 120ccaf79..bbd692a5b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/token/Token.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Token.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.token; +package touch.baton.domain.oauth.command.token; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/token/Tokens.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Tokens.java similarity index 66% rename from backend/baton/src/main/java/touch/baton/domain/oauth/token/Tokens.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Tokens.java index aa7287134..d66ae0d85 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/token/Tokens.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Tokens.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.token; +package touch.baton.domain.oauth.command.token; public record Tokens( AccessToken accessToken, diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/token/exception/RefreshTokenDomainException.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/exception/RefreshTokenDomainException.java similarity index 79% rename from backend/baton/src/main/java/touch/baton/domain/oauth/token/exception/RefreshTokenDomainException.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/command/token/exception/RefreshTokenDomainException.java index 6f72e72de..ab106c01d 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/token/exception/RefreshTokenDomainException.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/exception/RefreshTokenDomainException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.token.exception; +package touch.baton.domain.oauth.command.token.exception; import touch.baton.domain.common.exception.DomainException; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthMemberPrincipal.java b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthMemberPrincipal.java similarity index 83% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthMemberPrincipal.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthMemberPrincipal.java index 775963b1c..9db9b8d0f 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthMemberPrincipal.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthMemberPrincipal.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.controller.resolver; +package touch.baton.domain.oauth.query.controller.resolver; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthMemberPrincipalArgumentResolver.java b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthMemberPrincipalArgumentResolver.java similarity index 61% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthMemberPrincipalArgumentResolver.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthMemberPrincipalArgumentResolver.java index ec066b4ed..7cd9bb7e4 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthMemberPrincipalArgumentResolver.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthMemberPrincipalArgumentResolver.java @@ -1,27 +1,27 @@ -package touch.baton.domain.oauth.controller.resolver; +package touch.baton.domain.oauth.query.controller.resolver; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.exception.OauthRequestException; -import touch.baton.domain.oauth.repository.OauthMemberRepository; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.exception.OauthRequestException; +import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; import touch.baton.infra.auth.jwt.JwtDecoder; @Component public class AuthMemberPrincipalArgumentResolver extends UserPrincipalArgumentResolver { - private final OauthMemberRepository oauthMemberRepository; + private final OauthMemberCommandRepository oauthMemberCommandRepository; - public AuthMemberPrincipalArgumentResolver(final JwtDecoder jwtDecoder, final OauthMemberRepository oauthMemberRepository) { + public AuthMemberPrincipalArgumentResolver(final JwtDecoder jwtDecoder, final OauthMemberCommandRepository oauthMemberCommandRepository) { super(jwtDecoder); - this.oauthMemberRepository = oauthMemberRepository; + this.oauthMemberCommandRepository = oauthMemberCommandRepository; } @Override @@ -43,7 +43,7 @@ protected Object getGuest() { @Override protected Object getUser(final String socialId) { - return oauthMemberRepository.findBySocialId(new SocialId(socialId)) + return oauthMemberCommandRepository.findBySocialId(new SocialId(socialId)) .orElseThrow(() -> new OauthRequestException(ClientErrorCode.JWT_CLAIM_SOCIAL_ID_IS_WRONG)); } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthRunnerPrincipal.java b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthRunnerPrincipal.java similarity index 83% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthRunnerPrincipal.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthRunnerPrincipal.java index d44f4b40a..1b5995033 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthRunnerPrincipal.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthRunnerPrincipal.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.controller.resolver; +package touch.baton.domain.oauth.query.controller.resolver; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthRunnerPrincipalArgumentResolver.java b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthRunnerPrincipalArgumentResolver.java similarity index 61% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthRunnerPrincipalArgumentResolver.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthRunnerPrincipalArgumentResolver.java index bdffe6038..71af5ec54 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthRunnerPrincipalArgumentResolver.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthRunnerPrincipalArgumentResolver.java @@ -1,29 +1,28 @@ -package touch.baton.domain.oauth.controller.resolver; +package touch.baton.domain.oauth.query.controller.resolver; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.exception.OauthRequestException; -import touch.baton.domain.oauth.repository.OauthRunnerRepository; -import touch.baton.domain.runner.Runner; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.exception.OauthRequestException; +import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; import touch.baton.infra.auth.jwt.JwtDecoder; @Component public class AuthRunnerPrincipalArgumentResolver extends UserPrincipalArgumentResolver { - private final OauthRunnerRepository oauthRunnerRepository; + private final OauthRunnerCommandRepository oauthRunnerCommandRepository; - public AuthRunnerPrincipalArgumentResolver(final JwtDecoder jwtDecoder, final OauthRunnerRepository oauthRunnerRepository) { + public AuthRunnerPrincipalArgumentResolver(final JwtDecoder jwtDecoder, final OauthRunnerCommandRepository oauthRunnerCommandRepository) { super(jwtDecoder); - this.oauthRunnerRepository = oauthRunnerRepository; + this.oauthRunnerCommandRepository = oauthRunnerCommandRepository; } @Override @@ -47,7 +46,7 @@ protected Object getGuest() { @Override protected Object getUser(final String socialId) { - return oauthRunnerRepository.joinByMemberSocialId(new SocialId(socialId)) + return oauthRunnerCommandRepository.joinByMemberSocialId(new SocialId(socialId)) .orElseThrow(() -> new OauthRequestException(ClientErrorCode.JWT_CLAIM_SOCIAL_ID_IS_WRONG)); } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthSupporterPrincipal.java b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthSupporterPrincipal.java similarity index 83% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthSupporterPrincipal.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthSupporterPrincipal.java index ae187010b..95db43948 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthSupporterPrincipal.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthSupporterPrincipal.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.controller.resolver; +package touch.baton.domain.oauth.query.controller.resolver; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthSupporterPrincipalArgumentResolver.java b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthSupporterPrincipalArgumentResolver.java similarity index 61% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthSupporterPrincipalArgumentResolver.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthSupporterPrincipalArgumentResolver.java index b126b104d..351ce9fb7 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/AuthSupporterPrincipalArgumentResolver.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/AuthSupporterPrincipalArgumentResolver.java @@ -1,15 +1,14 @@ -package touch.baton.domain.oauth.controller.resolver; +package touch.baton.domain.oauth.query.controller.resolver; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.exception.OauthRequestException; -import touch.baton.domain.oauth.repository.OauthSupporterRepository; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.vo.ReviewCount; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.exception.OauthRequestException; +import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; import touch.baton.infra.auth.jwt.JwtDecoder; import java.util.Collections; @@ -17,11 +16,11 @@ @Component public class AuthSupporterPrincipalArgumentResolver extends UserPrincipalArgumentResolver { - private final OauthSupporterRepository oauthSupporterRepository; + private final OauthSupporterCommandRepository oauthSupporterCommandRepository; - public AuthSupporterPrincipalArgumentResolver(final JwtDecoder jwtDecoder, final OauthSupporterRepository oauthSupporterRepository) { + public AuthSupporterPrincipalArgumentResolver(final JwtDecoder jwtDecoder, final OauthSupporterCommandRepository oauthSupporterCommandRepository) { super(jwtDecoder); - this.oauthSupporterRepository = oauthSupporterRepository; + this.oauthSupporterCommandRepository = oauthSupporterCommandRepository; } @Override @@ -40,7 +39,7 @@ protected Object getGuest() { @Override protected Object getUser(final String socialId) { - return oauthSupporterRepository.joinByMemberSocialId(new SocialId(socialId)) + return oauthSupporterCommandRepository.joinByMemberSocialId(new SocialId(socialId)) .orElseThrow(() -> new OauthRequestException(ClientErrorCode.JWT_CLAIM_SOCIAL_ID_IS_WRONG)); } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/UserPrincipalArgumentResolver.java b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/UserPrincipalArgumentResolver.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/UserPrincipalArgumentResolver.java rename to backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/UserPrincipalArgumentResolver.java index c855f9254..0b9ff74d3 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/controller/resolver/UserPrincipalArgumentResolver.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/query/controller/resolver/UserPrincipalArgumentResolver.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.controller.resolver; +package touch.baton.domain.oauth.query.controller.resolver; import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; @@ -8,8 +8,8 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.oauth.AuthorizationHeader; -import touch.baton.domain.oauth.exception.OauthRequestException; +import touch.baton.domain.oauth.command.AuthorizationHeader; +import touch.baton.domain.oauth.command.exception.OauthRequestException; import touch.baton.infra.auth.jwt.JwtDecoder; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthMemberRepository.java b/backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthMemberRepository.java deleted file mode 100644 index a0f81ce75..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/OauthMemberRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package touch.baton.domain.oauth.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; - -import java.util.Optional; - -public interface OauthMemberRepository extends JpaRepository { - - Optional findMemberByOauthId(final OauthId oauthId); - - Optional findBySocialId(final SocialId socialId); -} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/RefreshTokenRepository.java b/backend/baton/src/main/java/touch/baton/domain/oauth/repository/RefreshTokenRepository.java deleted file mode 100644 index b202ee63f..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/repository/RefreshTokenRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package touch.baton.domain.oauth.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; - -import java.util.Optional; - -public interface RefreshTokenRepository extends JpaRepository { - - Optional findByToken(final Token token); - - Optional findByMember(final Member member); -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/controller/RunnerProfileController.java b/backend/baton/src/main/java/touch/baton/domain/runner/controller/RunnerProfileController.java deleted file mode 100644 index 7f8ebb0bf..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runner/controller/RunnerProfileController.java +++ /dev/null @@ -1,63 +0,0 @@ -package touch.baton.domain.runner.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -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.util.UriComponentsBuilder; -import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipal; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.controller.response.RunnerMyProfileResponse; -import touch.baton.domain.runner.controller.response.RunnerProfileResponse; -import touch.baton.domain.runner.controller.response.RunnerResponse; -import touch.baton.domain.runner.service.RunnerService; -import touch.baton.domain.runner.service.dto.RunnerUpdateRequest; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.service.RunnerPostService; - -import java.net.URI; -import java.util.List; - -@RequiredArgsConstructor -@RequestMapping("/api/v1/profile/runner") -@RestController -public class RunnerProfileController { - - private final RunnerPostService runnerPostService; - private final RunnerService runnerService; - - @GetMapping - public ResponseEntity readMyProfile(@AuthRunnerPrincipal final Runner runner) { - final RunnerResponse.Mine me = RunnerResponse.Mine.from(runner); - final List runnerPosts = runnerPostService.readRunnerPostsByRunnerId(runner.getId()).stream() - .map(RunnerPostResponse.Mine::from) - .toList(); - return ResponseEntity.ok(new RunnerMyProfileResponse(me, runnerPosts)); - } - - @GetMapping("/me") - public ResponseEntity readMyProfileByToken(@AuthRunnerPrincipal Runner runner) { - final RunnerResponse.MyProfile response = RunnerResponse.MyProfile.from(runner); - - return ResponseEntity.ok(response); - } - - @PatchMapping("/me") - public ResponseEntity updateMyProfile(@AuthRunnerPrincipal final Runner runner, - @RequestBody @Valid final RunnerUpdateRequest runnerUpdateRequest) { - runnerService.updateRunner(runner, runnerUpdateRequest); - final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/profile/runner/me").build().toUri(); - return ResponseEntity.noContent().location(redirectUri).build(); - } - - @GetMapping("/{runnerId}") - public ResponseEntity readRunnerProfile(@PathVariable Long runnerId) { - final Runner runner = runnerService.readByRunnerId(runnerId); - return ResponseEntity.ok(RunnerProfileResponse.Detail.from(runner)); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerMyProfileResponse.java b/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerMyProfileResponse.java deleted file mode 100644 index 3737ef318..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerMyProfileResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package touch.baton.domain.runner.controller.response; - -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; - -import java.util.List; - -public record RunnerMyProfileResponse(RunnerResponse.Mine profile, - List runnerPosts -) { -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerProfileResponse.java b/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerProfileResponse.java deleted file mode 100644 index c39dc9fd0..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runner/controller/response/RunnerProfileResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package touch.baton.domain.runner.controller.response; - -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; - -import java.util.List; -import java.util.Objects; - -public record RunnerProfileResponse() { - - public record Detail(Long runnerId, - String name, - String imageUrl, - String githubUrl, - String introduction, - String company, - List technicalTags - ) { - - public static Detail from(final Runner runner) { - final List tagNames = runner.getRunnerTechnicalTags().getRunnerTechnicalTags().stream() - .map(runnerTechnicalTag -> runnerTechnicalTag.getTechnicalTag().getTagName().getValue()) - .toList(); - - final Member member = runner.getMember(); - return new Detail(runner.getId(), - member.getMemberName().getValue(), - member.getImageUrl().getValue(), - member.getGithubUrl().getValue(), - runner.getIntroduction().getValue(), - member.getCompany().getValue(), - tagNames); - } - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/RunnerPost.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java similarity index 86% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/RunnerPost.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java index f20467a24..bc5b983bc 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/RunnerPost.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost; +package touch.baton.domain.runnerpost.command; import jakarta.persistence.Column; import jakarta.persistence.Embedded; @@ -13,21 +13,21 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.BaseEntity; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.exception.RunnerPostDomainException; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.RunnerPostTags; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.exception.RunnerPostDomainException; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.command.vo.Title; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.RunnerPostTags; import java.time.LocalDateTime; import java.util.ArrayList; @@ -38,10 +38,6 @@ import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.DONE; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.IN_PROGRESS; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.NOT_STARTED; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.OVERDUE; @Getter @NoArgsConstructor(access = PROTECTED) @@ -154,7 +150,7 @@ public static RunnerPost newInstance(final String title, .pullRequestUrl(new PullRequestUrl(pullRequestUrl)) .deadline(new Deadline(deadline)) .watchedCount(WatchedCount.zero()) - .reviewStatus(NOT_STARTED) + .reviewStatus(ReviewStatus.NOT_STARTED) .isReviewed(IsReviewed.notReviewed()) .runner(runner) .runnerPostTags(new RunnerPostTags(new ArrayList<>())) @@ -213,7 +209,7 @@ public void addAllRunnerPostTags(final List postTags) { } public void finishReview() { - updateReviewStatus(DONE); + updateReviewStatus(ReviewStatus.DONE); } public void finishFeedback() { @@ -221,22 +217,22 @@ public void finishFeedback() { } public void updateReviewStatus(final ReviewStatus other) { - if (this.reviewStatus.isSame(NOT_STARTED) && other.isSame(IN_PROGRESS)) { + if (this.reviewStatus.isSame(ReviewStatus.NOT_STARTED) && other.isSame(ReviewStatus.IN_PROGRESS)) { throw new RunnerPostDomainException("ReviewStatus 를 수정하던 도중 NOT_STARTED 에서 IN_PROGRESS 로 리뷰 상태 정책을 원인으로 실패하였습니다."); } - if (this.reviewStatus.isSame(NOT_STARTED) && other.isSame(DONE)) { + if (this.reviewStatus.isSame(ReviewStatus.NOT_STARTED) && other.isSame(ReviewStatus.DONE)) { throw new RunnerPostDomainException("ReviewStatus 를 수정하던 도중 NOT_STARTED 에서 DONE 으로 리뷰 상태 정책을 원인으로 실패하였습니다."); } - if (this.reviewStatus.isSame(DONE) && other.isSame(NOT_STARTED)) { + if (this.reviewStatus.isSame(ReviewStatus.DONE) && other.isSame(ReviewStatus.NOT_STARTED)) { throw new RunnerPostDomainException("ReviewStatus 를 수정하던 도중 DONE 에서 NOT_STARTED 로 리뷰 상태 정책을 원인으로 실패하였습니다."); } - if (this.reviewStatus.isSame(DONE) && other.isSame(IN_PROGRESS)) { + if (this.reviewStatus.isSame(ReviewStatus.DONE) && other.isSame(ReviewStatus.IN_PROGRESS)) { throw new RunnerPostDomainException("ReviewStatus 를 수정하던 도중 DONE 에서 IN_PROGRESS 로 리뷰 상태 정책을 원인으로 실패하였습니다."); } - if (this.reviewStatus.isSame(DONE) && other.isSame(OVERDUE)) { + if (this.reviewStatus.isSame(ReviewStatus.DONE) && other.isSame(ReviewStatus.OVERDUE)) { throw new RunnerPostDomainException("ReviewStatus 를 수정하던 도중 DONE 에서 OVERDUE 로 리뷰 상태 정책을 원인으로 실패하였습니다."); } - if (this.reviewStatus.isSame(OVERDUE) && other.isSame(DONE)) { + if (this.reviewStatus.isSame(ReviewStatus.OVERDUE) && other.isSame(ReviewStatus.DONE)) { throw new RunnerPostDomainException("ReviewStatus 를 수정하던 도중 OVERDUE 에서 DONE 로 리뷰 상태 정책을 원인으로 실패하였습니다."); } if (this.reviewStatus.isSame(other)) { @@ -250,7 +246,7 @@ public void assignSupporter(final Supporter supporter) { if (Objects.nonNull(this.supporter)) { throw new RunnerPostDomainException("Supporter 를 할당하던 도중 RunnerPost 에 이미 다른 Supporter 가 할당되어 있는 것을 원인으로 실패하였습니다."); } - if (reviewStatus.isSame(OVERDUE)) { + if (reviewStatus.isSame(ReviewStatus.OVERDUE)) { throw new RunnerPostDomainException("Supporter 를 할당하던 도중 ReviewStatus 가 OVERDUE 상태가 원인으로 실패하였습니다."); } if (reviewStatus.isNotSameAsNotStarted()) { @@ -261,7 +257,7 @@ public void assignSupporter(final Supporter supporter) { } this.supporter = supporter; - this.reviewStatus = IN_PROGRESS; + this.reviewStatus = ReviewStatus.IN_PROGRESS; } public void increaseWatchedCount() { diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPostsApplicantCount.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPostsApplicantCount.java new file mode 100644 index 000000000..50c80b3f2 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPostsApplicantCount.java @@ -0,0 +1,46 @@ +package touch.baton.domain.runnerpost.command; + +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.repository.dto.RunnerPostApplicantCountDto; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class RunnerPostsApplicantCount { + + private final Map runnerPostsApplicantCount; + + private RunnerPostsApplicantCount(final Map runnerPostsApplicantCount) { + this.runnerPostsApplicantCount = runnerPostsApplicantCount; + } + + public static RunnerPostsApplicantCount from(final List dtos) { + validateDtosNotNull(dtos); + final Map runnerPostApplicantCounts = dtos.stream() + .collect(Collectors.toMap( + RunnerPostApplicantCountDto::runnerPostId, + RunnerPostApplicantCountDto::applicantCount + )); + return new RunnerPostsApplicantCount(runnerPostApplicantCounts); + } + + private static void validateDtosNotNull(final List dtos) { + if (Objects.isNull(dtos)) { + throw new RunnerPostBusinessException("RunnerPostsApplicantCount 를 생성할 때 생성자인 dto 가 null 입니다."); + } + } + + public Long getApplicantCountById(final Long runnerPostId) { + final Long readApplicantCount = runnerPostsApplicantCount.get(runnerPostId); + validateElementExist(readApplicantCount); + return readApplicantCount; + } + + private void validateElementExist(final Long readElement) { + if (Objects.isNull(readElement)) { + throw new RunnerPostBusinessException(String.format("%s 에 없는 RunnerPostId 입니다.", this.getClass().getSimpleName())); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/controller/RunnerPostCommandController.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/controller/RunnerPostCommandController.java new file mode 100644 index 000000000..664570311 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/controller/RunnerPostCommandController.java @@ -0,0 +1,106 @@ +package touch.baton.domain.runnerpost.command.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +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.util.UriComponentsBuilder; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.oauth.query.controller.resolver.AuthRunnerPrincipal; +import touch.baton.domain.oauth.query.controller.resolver.AuthSupporterPrincipal; +import touch.baton.domain.runnerpost.command.service.RunnerPostCommandService; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostApplicantCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; + +import java.net.URI; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/posts/runner") +@RestController +public class RunnerPostCommandController { + + private final RunnerPostCommandService runnerPostCommandService; + + @PostMapping + public ResponseEntity createRunnerPost(@AuthRunnerPrincipal final Runner runner, + @Valid @RequestBody final RunnerPostCreateRequest request + ) { + final Long savedId = runnerPostCommandService.createRunnerPost(runner, request); + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") + .path("/{id}") + .buildAndExpand(savedId) + .toUri(); + return ResponseEntity.created(redirectUri).build(); + } + + @DeleteMapping("/{runnerPostId}") + public ResponseEntity deleteByRunnerPostId(@AuthRunnerPrincipal final Runner runner, + @PathVariable final Long runnerPostId + ) { + runnerPostCommandService.deleteByRunnerPostId(runnerPostId, runner); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/{runnerPostId}/application") + public ResponseEntity createRunnerPostApplicant( + @AuthSupporterPrincipal final Supporter supporter, + @PathVariable final Long runnerPostId, + @RequestBody @Valid final RunnerPostApplicantCreateRequest request + ) { + runnerPostCommandService.createRunnerPostApplicant(supporter, request, runnerPostId); + + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") + .path("/{runnerPostId}") + .buildAndExpand(runnerPostId) + .toUri(); + + return ResponseEntity.created(redirectUri).build(); + } + + @PatchMapping("/{runnerPostId}/cancelation") + public ResponseEntity updateSupporterCancelRunnerPost(@AuthSupporterPrincipal final Supporter supporter, + @PathVariable final Long runnerPostId + ) { + runnerPostCommandService.deleteSupporterRunnerPost(supporter, runnerPostId); + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") + .path("/{runnerPostId}") + .buildAndExpand(runnerPostId) + .toUri(); + return ResponseEntity.noContent().location(redirectUri).build(); + } + + @PatchMapping("/{runnerPostId}/supporters") + public ResponseEntity updateRunnerPostAppliedSupporter(@AuthRunnerPrincipal final Runner runner, + @PathVariable final Long runnerPostId, + @Valid @RequestBody final RunnerPostUpdateRequest.SelectSupporter request + ) { + runnerPostCommandService.updateRunnerPostAppliedSupporter(runner, runnerPostId, request); + + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") + .path("/{runnerPostId}") + .buildAndExpand(runnerPostId) + .toUri(); + return ResponseEntity.noContent().location(redirectUri).build(); + } + + @PatchMapping("/{runnerPostId}/done") + public ResponseEntity updateRunnerPostReviewStatusDone(@AuthSupporterPrincipal final Supporter supporter, + @PathVariable final Long runnerPostId + ) { + runnerPostCommandService.updateRunnerPostReviewStatusDone(runnerPostId, supporter); + + final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") + .path("/{runnerPostId}") + .buildAndExpand(runnerPostId) + .toUri(); + return ResponseEntity.noContent().location(redirectUri).build(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostApplySupporterEvent.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostApplySupporterEvent.java new file mode 100644 index 000000000..2262029fd --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostApplySupporterEvent.java @@ -0,0 +1,4 @@ +package touch.baton.domain.runnerpost.command.event; + +public record RunnerPostApplySupporterEvent(Long runnerPostId) { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostAssignSupporterEvent.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostAssignSupporterEvent.java new file mode 100644 index 000000000..0f443233c --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostAssignSupporterEvent.java @@ -0,0 +1,4 @@ +package touch.baton.domain.runnerpost.command.event; + +public record RunnerPostAssignSupporterEvent(Long runnerPostId) { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostReviewStatusDoneEvent.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostReviewStatusDoneEvent.java new file mode 100644 index 000000000..60b84a5ef --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/event/RunnerPostReviewStatusDoneEvent.java @@ -0,0 +1,4 @@ +package touch.baton.domain.runnerpost.command.event; + +public record RunnerPostReviewStatusDoneEvent(Long runnerPostId) { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostBusinessException.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostBusinessException.java similarity index 80% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostBusinessException.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostBusinessException.java index f2fd5fc64..7501b2abb 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostBusinessException.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostBusinessException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception; +package touch.baton.domain.runnerpost.command.exception; import touch.baton.domain.common.exception.BusinessException; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostDomainException.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostDomainException.java similarity index 79% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostDomainException.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostDomainException.java index b17187b29..abd7c03c5 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostDomainException.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostDomainException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception; +package touch.baton.domain.runnerpost.command.exception; import touch.baton.domain.common.exception.DomainException; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostRequestException.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostRequestException.java similarity index 84% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostRequestException.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostRequestException.java index 8418f5f49..015883b55 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/RunnerPostRequestException.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/RunnerPostRequestException.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception; +package touch.baton.domain.runnerpost.command.exception; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.ClientRequestException; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/FutureValidator.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/FutureValidator.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/FutureValidator.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/FutureValidator.java index ae9ac82f3..704e76184 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/FutureValidator.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/FutureValidator.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception.validator; +package touch.baton.domain.runnerpost.command.exception.validator; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/MaxLengthValidator.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/MaxLengthValidator.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/MaxLengthValidator.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/MaxLengthValidator.java index c13fdade3..05d6bbc5f 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/MaxLengthValidator.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/MaxLengthValidator.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception.validator; +package touch.baton.domain.runnerpost.command.exception.validator; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/UrlValidator.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/UrlValidator.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/UrlValidator.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/UrlValidator.java index 6848e3dad..486c151db 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/UrlValidator.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/UrlValidator.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception.validator; +package touch.baton.domain.runnerpost.command.exception.validator; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidFuture.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidFuture.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidFuture.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidFuture.java index a20f50c71..fce07b991 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidFuture.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidFuture.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception.validator; +package touch.baton.domain.runnerpost.command.exception.validator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidMaxLength.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidMaxLength.java similarity index 92% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidMaxLength.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidMaxLength.java index 831ca6c3f..242a3dc54 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidMaxLength.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidMaxLength.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception.validator; +package touch.baton.domain.runnerpost.command.exception.validator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidNotUrl.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidNotUrl.java similarity index 91% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidNotUrl.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidNotUrl.java index 7579b2331..fe727797e 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/exception/validator/ValidNotUrl.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/exception/validator/ValidNotUrl.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception.validator; +package touch.baton.domain.runnerpost.command.exception.validator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/RunnerPostCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/RunnerPostCommandRepository.java new file mode 100644 index 000000000..99f9cae6e --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/RunnerPostCommandRepository.java @@ -0,0 +1,7 @@ +package touch.baton.domain.runnerpost.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; + +public interface RunnerPostCommandRepository extends JpaRepository { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/dto/RunnerPostApplicantCountDto.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/dto/RunnerPostApplicantCountDto.java new file mode 100644 index 000000000..1b9f66cc6 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/repository/dto/RunnerPostApplicantCountDto.java @@ -0,0 +1,4 @@ +package touch.baton.domain.runnerpost.command.repository.dto; + +public record RunnerPostApplicantCountDto(Long runnerPostId, long applicantCount) { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java new file mode 100644 index 000000000..6d6d2a292 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java @@ -0,0 +1,177 @@ +package touch.baton.domain.runnerpost.command.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.member.command.repository.SupporterCommandRepository; +import touch.baton.domain.member.command.repository.SupporterRunnerPostCommandRepository; +import touch.baton.domain.member.command.vo.Message; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.event.RunnerPostApplySupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostAssignSupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostReviewStatusDoneEvent; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.repository.RunnerPostCommandRepository; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostApplicantCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.repository.TagCommandRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@RequiredArgsConstructor +@Transactional +@Service +public class RunnerPostCommandService { + + private final RunnerPostCommandRepository runnerPostCommandRepository; + private final TagCommandRepository tagCommandRepository; + private final SupporterCommandRepository supporterCommandRepository; + private final SupporterRunnerPostCommandRepository supporterRunnerPostCommandRepository; + private final ApplicationEventPublisher eventPublisher; + + public Long createRunnerPost(final Runner runner, final RunnerPostCreateRequest request) { + final RunnerPost runnerPost = toDomain(runner, request); + runnerPostCommandRepository.save(runnerPost); + + final List tags = findTagsAfterSave(request.tags()); + + final List runnerPostTags = tags.stream() + .map(tag -> RunnerPostTag.builder() + .tag(tag) + .runnerPost(runnerPost).build()) + .toList(); + + runnerPost.addAllRunnerPostTags(runnerPostTags); + return runnerPost.getId(); + } + + private RunnerPost toDomain(final Runner runner, final RunnerPostCreateRequest request) { + return RunnerPost.newInstance(request.title(), + request.implementedContents(), + request.curiousContents(), + request.postscriptContents(), + request.pullRequestUrl(), + request.deadline(), + runner); + } + + private List findTagsAfterSave(final List tagNames) { + final List tags = new ArrayList<>(); + for (String tagName : tagNames) { + tagCommandRepository.findByTagName(new TagName(tagName)) + .ifPresentOrElse(tags::add, addTagAfterSave(tags, tagName)); + } + + return tags; + } + + private Runnable addTagAfterSave(final List tags, final String tagName) { + return () -> { + final Tag savedTag = tagCommandRepository.save(Tag.newInstance(tagName)); + tags.add(savedTag); + }; + } + + public void deleteByRunnerPostId(final Long runnerPostId, final Runner runner) { + final RunnerPost runnerPost = runnerPostCommandRepository.findById(runnerPostId) + .orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 삭제할 러너 게시글이 존재하지 않습니다.")); + if (runnerPost.isNotOwner(runner)) { + throw new RunnerPostBusinessException("RunnerPost 를 게시한 유저가 아닙니다."); + } + if (runnerPost.isReviewStatusStarted()) { + throw new RunnerPostBusinessException("삭제할 수 없는 상태의 리뷰 상태입니다."); + } + if (supporterRunnerPostCommandRepository.existsByRunnerPostId(runnerPostId)) { + throw new RunnerPostBusinessException("지원자가 존재하여 삭제할 수 없습니다."); + } + runnerPostCommandRepository.deleteById(runnerPostId); + } + + private RunnerPost getRunnerPostOrThrowException(final Long runnerPostId) { + return runnerPostCommandRepository.findById(runnerPostId) + .orElseThrow(() -> new RunnerPostBusinessException("해당 runnerPostId 로 러너 게시글을 찾을 수 없습니다. runnerPostId 를 다시 확인해주세요")); + } + + public Long createRunnerPostApplicant(final Supporter supporter, + final RunnerPostApplicantCreateRequest request, + final Long runnerPostId + ) { + final RunnerPost foundRunnerPost = getRunnerPostOrThrowException(runnerPostId); + if (isApplySupporter(foundRunnerPost, supporter)) { + throw new RunnerPostBusinessException("Supporter 는 이미 해당 RunnerPost 에 리뷰 신청을 한 이력이 있습니다."); + } + + final SupporterRunnerPost runnerPostApplicant = SupporterRunnerPost.builder() + .supporter(supporter) + .runnerPost(foundRunnerPost) + .message(new Message(request.message())) + .build(); + + final Long savedApplicantId = supporterRunnerPostCommandRepository.save(runnerPostApplicant).getId(); + + eventPublisher.publishEvent(new RunnerPostApplySupporterEvent(foundRunnerPost.getId())); + + return savedApplicantId; + } + + public void updateRunnerPostReviewStatusDone(final Long runnerPostId, final Supporter supporter) { + final RunnerPost foundRunnerPost = runnerPostCommandRepository.findById(runnerPostId) + .orElseThrow(() -> new RunnerPostBusinessException("해당 식별자의 러너 게시글이 존재하지 않습니다.")); + + if (Objects.isNull(foundRunnerPost.getSupporter())) { + throw new RunnerPostBusinessException("아직 서포터가 배정이 안 된 게시글 입니다."); + } + + if (foundRunnerPost.isDifferentSupporter(supporter)) { + throw new RunnerPostBusinessException("다른 사람이 리뷰 중인 게시글의 상태를 변경할 수 없습니다."); + } + + foundRunnerPost.finishReview(); + + eventPublisher.publishEvent(new RunnerPostReviewStatusDoneEvent(foundRunnerPost.getId())); + } + + public void deleteSupporterRunnerPost(final Supporter supporter, final Long runnerPostId) { + final RunnerPost runnerPost = runnerPostCommandRepository.findById(runnerPostId) + .orElseThrow(() -> new RunnerPostBusinessException("존재하지 않는 RunnerPost 입니다.")); + if (!runnerPost.isReviewStatusNotStarted()) { + throw new RunnerPostBusinessException("이미 진행 중인 러너 게시글의 서포터 지원은 철회할 수 없습니다."); + } + supporterRunnerPostCommandRepository.deleteBySupporterIdAndRunnerPostId(supporter.getId(), runnerPostId); + } + + public void updateRunnerPostAppliedSupporter(final Runner runner, + final Long runnerPostId, + final RunnerPostUpdateRequest.SelectSupporter request + ) { + final Supporter foundApplySupporter = supporterCommandRepository.findById(request.supporterId()) + .orElseThrow(() -> new RunnerPostBusinessException("해당하는 식별자값의 서포터를 찾을 수 없습니다.")); + final RunnerPost foundRunnerPost = runnerPostCommandRepository.findById(runnerPostId) + .orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다.")); + + if (!isApplySupporter(foundRunnerPost, foundApplySupporter)) { + throw new RunnerPostBusinessException("게시글에 리뷰를 제안한 서포터가 아닙니다."); + } + if (foundRunnerPost.isNotOwner(runner)) { + throw new RunnerPostBusinessException("RunnerPost 의 글쓴이와 다른 사용자입니다."); + } + + foundRunnerPost.assignSupporter(foundApplySupporter); + + eventPublisher.publishEvent(new RunnerPostAssignSupporterEvent(foundRunnerPost.getId())); + } + + private boolean isApplySupporter(final RunnerPost runnerPost, final Supporter supporter) { + return supporterRunnerPostCommandRepository.existsByRunnerPostIdAndSupporterId(runnerPost.getId(), supporter.getId()); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostApplicantCreateRequest.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostApplicantCreateRequest.java similarity index 65% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostApplicantCreateRequest.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostApplicantCreateRequest.java index 9a3446635..99eceb52d 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostApplicantCreateRequest.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostApplicantCreateRequest.java @@ -1,7 +1,7 @@ -package touch.baton.domain.runnerpost.service.dto; +package touch.baton.domain.runnerpost.command.service.dto; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.runnerpost.exception.validator.ValidMaxLength; +import touch.baton.domain.runnerpost.command.exception.validator.ValidMaxLength; public record RunnerPostApplicantCreateRequest(@ValidMaxLength(clientErrorCode = ClientErrorCode.APPLICANT_MESSAGE_IS_OVERFLOW, max = 500) String message diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostCreateRequest.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostCreateRequest.java similarity index 85% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostCreateRequest.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostCreateRequest.java index 52f5d3e3b..e26baee83 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostCreateRequest.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostCreateRequest.java @@ -1,9 +1,9 @@ -package touch.baton.domain.runnerpost.service.dto; +package touch.baton.domain.runnerpost.command.service.dto; import touch.baton.domain.common.exception.validator.ValidNotNull; -import touch.baton.domain.runnerpost.exception.validator.ValidFuture; -import touch.baton.domain.runnerpost.exception.validator.ValidMaxLength; -import touch.baton.domain.runnerpost.exception.validator.ValidNotUrl; +import touch.baton.domain.runnerpost.command.exception.validator.ValidFuture; +import touch.baton.domain.runnerpost.command.exception.validator.ValidMaxLength; +import touch.baton.domain.runnerpost.command.exception.validator.ValidNotUrl; import java.time.LocalDateTime; import java.util.List; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostUpdateRequest.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostUpdateRequest.java similarity index 85% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostUpdateRequest.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostUpdateRequest.java index 2c2720038..e4f528f36 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostUpdateRequest.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/dto/RunnerPostUpdateRequest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.service.dto; +package touch.baton.domain.runnerpost.command.service.dto; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.validator.ValidNotNull; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/CuriousContents.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/CuriousContents.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/CuriousContents.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/CuriousContents.java index 0cb2ebc19..0f01a56a0 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/CuriousContents.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/CuriousContents.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/Deadline.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/Deadline.java similarity index 95% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/Deadline.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/Deadline.java index 14cb5fff4..2eba007dc 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/Deadline.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/Deadline.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/ImplementedContents.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/ImplementedContents.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/ImplementedContents.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/ImplementedContents.java index 09abd6149..bc55c11af 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/ImplementedContents.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/ImplementedContents.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/IsReviewed.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/IsReviewed.java similarity index 93% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/IsReviewed.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/IsReviewed.java index e96528fa0..6870f9848 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/IsReviewed.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/IsReviewed.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/PostscriptContents.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/PostscriptContents.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/PostscriptContents.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/PostscriptContents.java index b98c64089..e00074869 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/PostscriptContents.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/PostscriptContents.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/PullRequestUrl.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/PullRequestUrl.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/PullRequestUrl.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/PullRequestUrl.java index ece053285..5dee19f74 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/PullRequestUrl.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/PullRequestUrl.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/ReviewStatus.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/ReviewStatus.java similarity index 82% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/ReviewStatus.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/ReviewStatus.java index da8916bb6..4a3109ac5 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/vo/ReviewStatus.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/ReviewStatus.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import static java.util.Locale.ENGLISH; @@ -17,10 +17,6 @@ public boolean isSame(final ReviewStatus reviewStatus) { return this == reviewStatus; } - public boolean isSameAsNotStarted() { - return this == NOT_STARTED; - } - public boolean isNotSameAsNotStarted() { return this != NOT_STARTED; } diff --git a/backend/baton/src/main/java/touch/baton/domain/common/vo/Title.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/Title.java similarity index 93% rename from backend/baton/src/main/java/touch/baton/domain/common/vo/Title.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/Title.java index 55a92b6f6..0b890b4e5 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/vo/Title.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/Title.java @@ -1,4 +1,4 @@ -package touch.baton.domain.common.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/common/vo/WatchedCount.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/WatchedCount.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/common/vo/WatchedCount.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/WatchedCount.java index a1edb67a1..7aa370354 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/vo/WatchedCount.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/vo/WatchedCount.java @@ -1,4 +1,4 @@ -package touch.baton.domain.common.vo; +package touch.baton.domain.runnerpost.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java deleted file mode 100644 index 803efd134..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostController.java +++ /dev/null @@ -1,255 +0,0 @@ -package touch.baton.domain.runnerpost.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -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.util.UriComponentsBuilder; -import touch.baton.domain.common.response.PageResponse; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.controller.resolver.AuthMemberPrincipal; -import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipal; -import touch.baton.domain.oauth.controller.resolver.AuthSupporterPrincipal; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.controller.response.SupporterRunnerPostResponse; -import touch.baton.domain.runnerpost.controller.response.SupporterRunnerPostResponses; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.service.dto.RunnerPostApplicantCreateRequest; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; -import touch.baton.domain.runnerpost.service.dto.RunnerPostUpdateRequest; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; - -import java.net.URI; -import java.util.List; -import java.util.stream.IntStream; - -import static org.springframework.data.domain.Sort.Direction.DESC; - -@RequiredArgsConstructor -@RequestMapping("/api/v1/posts/runner") -@RestController -public class RunnerPostController { - - private final RunnerPostService runnerPostService; - - @PostMapping - public ResponseEntity createRunnerPost(@AuthRunnerPrincipal final Runner runner, - @Valid @RequestBody final RunnerPostCreateRequest request - ) { - final Long savedId = runnerPostService.createRunnerPost(runner, request); - final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") - .path("/{id}") - .buildAndExpand(savedId) - .toUri(); - return ResponseEntity.created(redirectUri).build(); - } - - @GetMapping("/{runnerPostId}") - public ResponseEntity readByRunnerPostId( - @AuthMemberPrincipal(required = false) final Member member, - @PathVariable final Long runnerPostId - ) { - final RunnerPost foundRunnerPost = runnerPostService.readByRunnerPostId(runnerPostId); - final long applicantCount = runnerPostService.readCountByRunnerPostId(foundRunnerPost.getId()); - final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId(runnerPostId, member.getId()); - - runnerPostService.increaseWatchedCount(foundRunnerPost); - final RunnerPostResponse.Detail response = RunnerPostResponse.Detail.of( - foundRunnerPost, - foundRunnerPost.isOwner(member), - isApplicantHistoryExist, - applicantCount - ); - - return ResponseEntity.ok(response); - } - - @DeleteMapping("/{runnerPostId}") - public ResponseEntity deleteByRunnerPostId(@AuthRunnerPrincipal final Runner runner, - @PathVariable final Long runnerPostId - ) { - runnerPostService.deleteByRunnerPostId(runnerPostId, runner); - return ResponseEntity.noContent().build(); - } - - @GetMapping - public ResponseEntity> readRunnerPostsByReviewStatus( - @PageableDefault(sort = "id", direction = DESC) final Pageable pageable, - @RequestParam("reviewStatus") final ReviewStatus reviewStatus - ) { - final Page pageRunnerPosts = runnerPostService.readRunnerPostsByReviewStatus(pageable, reviewStatus); - final List foundRunnerPosts = pageRunnerPosts.getContent(); - final List applicantCounts = collectApplicantCounts(pageRunnerPosts); - final List responses = IntStream.range(0, foundRunnerPosts.size()) - .mapToObj(index -> { - final RunnerPost runnerPost = foundRunnerPosts.get(index); - final Long applicantCount = applicantCounts.get(index); - - return RunnerPostResponse.Simple.from(runnerPost, applicantCount); - }).toList(); - - final Page pageResponse - = new PageImpl<>(responses, pageable, pageRunnerPosts.getTotalElements()); - - return ResponseEntity.ok(PageResponse.from(pageResponse)); - } - - @GetMapping("/search") - public ResponseEntity> readReferencedBySupporter( - @PageableDefault(sort = "id", direction = DESC) final Pageable pageable, - @RequestParam("supporterId") final Long supporterId, - @RequestParam("reviewStatus") final ReviewStatus reviewStatus - ) { - final Page pageRunnerPosts = runnerPostService.readRunnerPostsBySupporterIdAndReviewStatus(pageable, supporterId, reviewStatus); - final List foundRunnerPosts = pageRunnerPosts.getContent(); - final List applicantCounts = collectApplicantCounts(pageRunnerPosts); - final List responses = IntStream.range(0, foundRunnerPosts.size()) - .mapToObj(index -> { - final RunnerPost foundRunnerPost = foundRunnerPosts.get(index); - final long applicantCount = applicantCounts.get(index); - - return RunnerPostResponse.ReferencedBySupporter.of(foundRunnerPost, applicantCount); - }).toList(); - - final Page pageResponse - = new PageImpl<>(responses, pageable, pageRunnerPosts.getTotalElements()); - - return ResponseEntity.ok(PageResponse.from(pageResponse)); - } - - @GetMapping("/{runnerPostId}/supporters") - public ResponseEntity readSupporterRunnerPostsByRunnerPostId( - @AuthRunnerPrincipal final Runner runner, - @PathVariable final Long runnerPostId - ) { - final List responses = runnerPostService.readSupporterRunnerPostsByRunnerPostId(runner, runnerPostId).stream() - .map(SupporterRunnerPostResponse.Detail::from) - .toList(); - - return ResponseEntity.ok(SupporterRunnerPostResponses.Detail.from(responses)); - } - - @GetMapping("/me/supporter") - public ResponseEntity> readRunnerPostsByLoginedSupporterAndReviewStatus( - @PageableDefault(sort = "id", direction = DESC) final Pageable pageable, - @AuthSupporterPrincipal final Supporter supporter, - @RequestParam("reviewStatus") final ReviewStatus reviewStatus - ) { - final Page pageRunnerPosts = runnerPostService.readRunnerPostsBySupporterIdAndReviewStatus(pageable, supporter.getId(), reviewStatus); - final List foundRunnerPosts = pageRunnerPosts.getContent(); - final List applicantCounts = collectApplicantCounts(pageRunnerPosts); - final List responses = IntStream.range(0, foundRunnerPosts.size()) - .mapToObj(index -> { - final RunnerPost foundRunnerPost = foundRunnerPosts.get(index); - final Long applicantCount = applicantCounts.get(index); - - return RunnerPostResponse.ReferencedBySupporter.of(foundRunnerPost, applicantCount); - }).toList(); - - final Page pageResponse - = new PageImpl<>(responses, pageable, pageRunnerPosts.getTotalElements()); - - return ResponseEntity.ok(PageResponse.from(pageResponse)); - } - - @GetMapping("/me/runner") - public ResponseEntity> readRunnerMyPage( - @PageableDefault(sort = "id", direction = DESC) final Pageable pageable, - @AuthRunnerPrincipal final Runner runner, - @RequestParam("reviewStatus") final ReviewStatus reviewStatus - ) { - final Page pageRunnerPosts = runnerPostService.readRunnerPostsByRunnerIdAndReviewStatus(pageable, runner.getId(), reviewStatus); - final List applicantCounts = collectApplicantCounts(pageRunnerPosts); - - final List responses = IntStream.range(0, pageRunnerPosts.getContent().size()) - .mapToObj(index -> { - final Long applicantCount = applicantCounts.get(index); - final RunnerPost runnerPost = pageRunnerPosts.getContent().get(index); - return RunnerPostResponse.SimpleInMyPage.from(runnerPost, applicantCount); - } - ).toList(); - - final Page pageResponse - = new PageImpl<>(responses, pageable, pageRunnerPosts.getTotalElements()); - - return ResponseEntity.ok(PageResponse.from(pageResponse)); - } - - private List collectApplicantCounts(final Page pageRunnerPosts) { - final List runnerPostIds = pageRunnerPosts.stream() - .map(RunnerPost::getId) - .toList(); - - return runnerPostService.readCountsByRunnerPostIds(runnerPostIds); - } - - @PostMapping("/{runnerPostId}/application") - public ResponseEntity createRunnerPostApplicant( - @AuthSupporterPrincipal final Supporter supporter, - @PathVariable final Long runnerPostId, - @RequestBody @Valid final RunnerPostApplicantCreateRequest request - ) { - runnerPostService.createRunnerPostApplicant(supporter, request, runnerPostId); - - final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") - .path("/{runnerPostId}") - .buildAndExpand(runnerPostId) - .toUri(); - - return ResponseEntity.created(redirectUri).build(); - } - - @PatchMapping("/{runnerPostId}/cancelation") - public ResponseEntity updateSupporterCancelRunnerPost(@AuthSupporterPrincipal final Supporter supporter, - @PathVariable final Long runnerPostId - ) { - runnerPostService.deleteSupporterRunnerPost(supporter, runnerPostId); - final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") - .path("/{runnerPostId}") - .buildAndExpand(runnerPostId) - .toUri(); - return ResponseEntity.noContent().location(redirectUri).build(); - } - - @PatchMapping("/{runnerPostId}/supporters") - public ResponseEntity updateRunnerPostAppliedSupporter(@AuthRunnerPrincipal final Runner runner, - @PathVariable final Long runnerPostId, - @Valid @RequestBody final RunnerPostUpdateRequest.SelectSupporter request - ) { - runnerPostService.updateRunnerPostAppliedSupporter(runner, runnerPostId, request); - - final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") - .path("/{runnerPostId}") - .buildAndExpand(runnerPostId) - .toUri(); - return ResponseEntity.noContent().location(redirectUri).build(); - } - - @PatchMapping("/{runnerPostId}/done") - public ResponseEntity updateRunnerPostReviewStatusDone(@AuthSupporterPrincipal final Supporter supporter, - @PathVariable final Long runnerPostId - ) { - runnerPostService.updateRunnerPostReviewStatusDone(runnerPostId, supporter); - - final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/posts/runner") - .path("/{runnerPostId}") - .buildAndExpand(runnerPostId) - .toUri(); - return ResponseEntity.noContent().location(redirectUri).build(); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostReadController.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostReadController.java deleted file mode 100644 index 8d1558e8d..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/RunnerPostReadController.java +++ /dev/null @@ -1,71 +0,0 @@ -package touch.baton.domain.runnerpost.controller; - -import jakarta.annotation.Nullable; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import touch.baton.domain.common.response.PageResponse; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountMappingDto; -import touch.baton.domain.runnerpost.service.RunnerPostReadService; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.ReviewStatus; - -import java.util.List; - -import static org.springframework.data.domain.Sort.Direction.DESC; - -@RequiredArgsConstructor -@RequestMapping("/api/v1/posts/runner") -@RestController -public class RunnerPostReadController { - - private final RunnerPostReadService runnerPostReadService; - private final RunnerPostService runnerPostService; - - @GetMapping("/tags/search") - public ResponseEntity> readRunnerPostsByTagNamesAndReviewStatus( - @PageableDefault(sort = "id", direction = DESC) final Pageable pageable, - @Nullable @RequestParam(required = false) final String tagName, - @RequestParam final ReviewStatus reviewStatus - ) { - final Page pageRunnerPosts = getPageRunnerPosts(pageable, tagName, reviewStatus); - final ApplicantCountMappingDto applicantCountMapping = getApplicantCountMapping(pageRunnerPosts); - - final List responses = pageRunnerPosts.getContent().stream() - .map(runnerPost -> { - final Long foundApplicantCount = applicantCountMapping.getApplicantCountByRunnerPostId(runnerPost.getId()); - - return RunnerPostResponse.Simple.from(runnerPost, foundApplicantCount); - }).toList(); - - final PageImpl pageResponse - = new PageImpl<>(responses, pageable, pageRunnerPosts.getTotalElements()); - - return ResponseEntity.ok(PageResponse.from(pageResponse)); - } - - private Page getPageRunnerPosts(final Pageable pageable, final String tagName, final ReviewStatus reviewStatus) { - if (tagName == null || tagName.isBlank()) { - return runnerPostService.readRunnerPostsByReviewStatus(pageable, reviewStatus); - } - - return runnerPostReadService.readRunnerPostByTagNameAndReviewStatus(pageable, tagName, reviewStatus); - } - - private ApplicantCountMappingDto getApplicantCountMapping(final Page pageRunnerPosts) { - final List foundRunnerPostIds = pageRunnerPosts.stream() - .map(RunnerPost::getId) - .toList(); - - return runnerPostReadService.readApplicantCountMappingByRunnerPostIds(foundRunnerPostIds); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostReadResponses.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostReadResponses.java deleted file mode 100644 index 7270ed8c4..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostReadResponses.java +++ /dev/null @@ -1,27 +0,0 @@ -package touch.baton.domain.runnerpost.controller.response; - -import java.util.List; - -public record RunnerPostReadResponses() { - - public record NoFiltering(List data) { - - public static NoFiltering from(final List data) { - return new NoFiltering(data); - } - } - - public record SimpleInMyPage(List data) { - - public static SimpleInMyPage from(final List data) { - return new SimpleInMyPage(data); - } - } - - public record LoginedSupporter(List data) { - - public static LoginedSupporter from(final List data) { - return new LoginedSupporter(data); - } - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterResponseTestVersion.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterResponseTestVersion.java deleted file mode 100644 index 33ca01c86..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterResponseTestVersion.java +++ /dev/null @@ -1,16 +0,0 @@ -package touch.baton.domain.runnerpost.controller.response; - -import touch.baton.domain.supporter.Supporter; - -public record SupporterResponseTestVersion() { - - public record Simple(Long supporterId, String name) { - - public static Simple fromTestVersion(final Supporter supporter) { - return new Simple( - supporter.getId(), - supporter.getMember().getMemberName().getValue() - ); - } - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/RunnerPostQueryController.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/RunnerPostQueryController.java new file mode 100644 index 000000000..8b2e376cd --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/RunnerPostQueryController.java @@ -0,0 +1,128 @@ +package touch.baton.domain.runnerpost.query.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import touch.baton.domain.common.request.PageParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipal; +import touch.baton.domain.oauth.query.controller.resolver.AuthRunnerPrincipal; +import touch.baton.domain.oauth.query.controller.resolver.AuthSupporterPrincipal; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; +import touch.baton.domain.runnerpost.query.controller.response.SupporterRunnerPostResponse; +import touch.baton.domain.runnerpost.query.controller.response.SupporterRunnerPostResponses; +import touch.baton.domain.runnerpost.query.service.RunnerPostQueryService; + +import java.util.List; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/posts/runner") +@RestController +public class RunnerPostQueryController { + + private final RunnerPostQueryService runnerPostQueryService; + + @GetMapping + public ResponseEntity> readRunnerPostsByTagNameAndReviewStatus( + @Valid @ModelAttribute final PageParams pageParams, + @RequestParam(required = false) final String tagName, + @RequestParam(required = false) final ReviewStatus reviewStatus + ) { + return ResponseEntity.ok(runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus(tagName, pageParams, reviewStatus)); + } + + @GetMapping("/{runnerPostId}") + public ResponseEntity readByRunnerPostId( + @AuthMemberPrincipal(required = false) final Member member, + @PathVariable final Long runnerPostId + ) { + final RunnerPost foundRunnerPost = runnerPostQueryService.readByRunnerPostId(runnerPostId); + final long applicantCount = runnerPostQueryService.countApplicantsByRunnerPostId(foundRunnerPost.getId()); + final boolean isApplicantHistoryExist = runnerPostQueryService.existsRunnerPostApplicantByRunnerPostIdAndMemberId(runnerPostId, member.getId()); + + runnerPostQueryService.increaseWatchedCount(foundRunnerPost); + final RunnerPostResponse.Detail response = RunnerPostResponse.Detail.of( + foundRunnerPost, + foundRunnerPost.isOwner(member), + isApplicantHistoryExist, + applicantCount + ); + + return ResponseEntity.ok(response); + } + + @GetMapping("/search") + public ResponseEntity> readRunnerPostBySupporterIdAndReviewStatusDone( + @Valid @ModelAttribute final PageParams pageParams, + @RequestParam final Long supporterId + ) { + return ResponseEntity.ok(runnerPostQueryService.pageRunnerPostBySupporterIdAndReviewStatus(pageParams, supporterId, ReviewStatus.DONE)); + } + + @GetMapping("/search/count") + public ResponseEntity countRunnerPostBySupporterIdAndReviewStatusDone( + @RequestParam final Long supporterId + ) { + final long count = runnerPostQueryService.countRunnerPostBySupporterIdAndReviewStatus(supporterId, ReviewStatus.DONE); + return ResponseEntity.ok(RunnerPostResponse.Count.from(count)); + } + + @GetMapping("/{runnerPostId}/supporters") + public ResponseEntity readSupporterRunnerPostsByRunnerPostId( + @AuthRunnerPrincipal final Runner runner, + @PathVariable final Long runnerPostId + ) { + final List responses = runnerPostQueryService.readSupporterRunnerPostsByRunnerPostId(runner, runnerPostId).stream() + .map(SupporterRunnerPostResponse.Detail::from) + .toList(); + + return ResponseEntity.ok(SupporterRunnerPostResponses.Detail.from(responses)); + } + + @GetMapping("/me/supporter") + public ResponseEntity> readRunnerPostByLoginedSupporterAndReviewStatus( + @AuthSupporterPrincipal final Supporter supporter, + @Valid @ModelAttribute final PageParams pageParams, + @RequestParam(required = false) final ReviewStatus reviewStatus + ) { + return ResponseEntity.ok(runnerPostQueryService.pageRunnerPostBySupporterIdAndReviewStatus(pageParams, supporter.getId(), reviewStatus)); + } + + @GetMapping("/me/runner") + public ResponseEntity> readRunnerPostByLoginedRunnerAndReviewStatus( + @AuthRunnerPrincipal final Runner runner, + @Valid @ModelAttribute final PageParams pageParams, + @RequestParam(required = false) final ReviewStatus reviewStatus + ) { + return ResponseEntity.ok(runnerPostQueryService.pageRunnerPostByRunnerIdAndReviewStatus(pageParams, runner.getId(), reviewStatus)); + } + + @GetMapping("/me/supporter/count") + public ResponseEntity countRunnerPostByLoginedSupporterAndReviewStatus( + @AuthSupporterPrincipal final Supporter supporter, + @RequestParam final ReviewStatus reviewStatus + ) { + final long runnerPostCount = runnerPostQueryService.countRunnerPostBySupporterIdAndReviewStatus(supporter.getId(), reviewStatus); + return ResponseEntity.ok(RunnerPostResponse.Count.from(runnerPostCount)); + } + + @GetMapping("/me/runner/count") + public ResponseEntity countRunnerPostByLoginedRunnerAndReviewStatus( + @AuthRunnerPrincipal final Runner runner, + @RequestParam final ReviewStatus reviewStatus + ) { + final long runnerPostCount = runnerPostQueryService.countRunnerPostByRunnerIdAndReviewStatus(runner.getId(), reviewStatus); + return ResponseEntity.ok(RunnerPostResponse.Count.from(runnerPostCount)); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostResponse.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/RunnerPostResponse.java similarity index 53% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostResponse.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/RunnerPostResponse.java index 2d1ea2467..b6be711c4 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/RunnerPostResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/RunnerPostResponse.java @@ -1,8 +1,10 @@ -package touch.baton.domain.runnerpost.controller.response; +package touch.baton.domain.runnerpost.query.controller.response; -import touch.baton.domain.runner.controller.response.RunnerResponse; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.ReviewStatus; +import touch.baton.domain.common.response.IdExtractable; +import touch.baton.domain.member.query.controller.response.RunnerResponse; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.tag.command.RunnerPostTag; import java.time.LocalDateTime; import java.util.List; @@ -23,7 +25,7 @@ public record Detail(Long runnerPostId, boolean isOwner, boolean isApplied, List tags, - RunnerResponse.Detail runnerProfile + RunnerResponse.InRunnerPostDetail runnerProfile ) { public static Detail of(final RunnerPost runnerPost, @@ -45,7 +47,7 @@ public static Detail of(final RunnerPost runnerPost, isOwner, isApplied, convertToTags(runnerPost), - RunnerResponse.Detail.from(runnerPost.getRunner()) + RunnerResponse.InRunnerPostDetail.from(runnerPost.getRunner()) ); } } @@ -58,10 +60,11 @@ public record Simple(Long runnerPostId, String reviewStatus, RunnerResponse.Simple runnerProfile, List tags - ) { + ) implements IdExtractable { - public static Simple from(final RunnerPost runnerPost, - final long applicantCount + public static Simple of(final RunnerPost runnerPost, + final long applicantCount, + final List runnerPostTags ) { return new Simple( runnerPost.getId(), @@ -71,75 +74,42 @@ public static Simple from(final RunnerPost runnerPost, applicantCount, runnerPost.getReviewStatus().name(), RunnerResponse.Simple.from(runnerPost.getRunner()), - convertToTags(runnerPost) + convertToTags(runnerPost, runnerPostTags) ); } - } - - public record LoginedSupporter(Long runnerPostId, - String title, - LocalDateTime deadline, - List tags, - int watchedCount, - int applicantCount - ) { - - public static LoginedSupporter from(final RunnerPost runnerPost, final int applicantCount) { - return new LoginedSupporter( - runnerPost.getId(), - runnerPost.getTitle().getValue(), - runnerPost.getDeadline().getValue(), - convertToTags(runnerPost), - runnerPost.getWatchedCount().getValue(), - applicantCount - ); - } - } - - public record Mine(Long runnerPostId, - String title, - LocalDateTime deadline, - List tags, - String reviewStatus - ) { - - public static Mine from(final RunnerPost runnerPost) { - return new Mine( - runnerPost.getId(), - runnerPost.getTitle().getValue(), - runnerPost.getDeadline().getValue(), - convertToTags(runnerPost), - runnerPost.getReviewStatus().name() - ); + @Override + public Long extractId() { + return runnerPostId; } } - public record SimpleInMyPage(Long runnerPostId, - Long supporterId, + public record SimpleByRunner(Long runnerPostId, String title, LocalDateTime deadline, - List tags, int watchedCount, long applicantCount, String reviewStatus, - boolean isReviewed + boolean isReviewed, + Long supporterId, + List tags - ) { + ) implements IdExtractable { - public static SimpleInMyPage from(final RunnerPost runnerPost, - final long applicantCount + public static SimpleByRunner of(final RunnerPost runnerPost, + final long applicantCount, + final List runnerPostTags ) { - return new SimpleInMyPage( + return new SimpleByRunner( runnerPost.getId(), - getSupporterIdByRunnerPost(runnerPost), runnerPost.getTitle().getValue(), runnerPost.getDeadline().getValue(), - convertToTags(runnerPost), runnerPost.getWatchedCount().getValue(), applicantCount, runnerPost.getReviewStatus().name(), - runnerPost.getIsReviewed().getValue() + runnerPost.getIsReviewed().getValue(), + getSupporterIdByRunnerPost(runnerPost), + convertToTags(runnerPost, runnerPostTags) ); } @@ -149,27 +119,17 @@ private static Long getSupporterIdByRunnerPost(final RunnerPost runnerPost) { } return runnerPost.getSupporter().getId(); } + + @Override + public Long extractId() { + return runnerPostId; + } } - public record ReferencedBySupporter(Long runnerPostId, - String title, - LocalDateTime deadline, - List tags, - int watchedCount, - long applicantCount, - String reviewStatus - ) { + public record Count(Long count) { - public static ReferencedBySupporter of(final RunnerPost runnerPost, final long applicantCount) { - return new ReferencedBySupporter( - runnerPost.getId(), - runnerPost.getTitle().getValue(), - runnerPost.getDeadline().getValue(), - convertToTags(runnerPost), - runnerPost.getWatchedCount().getValue(), - applicantCount, - runnerPost.getReviewStatus().name() - ); + public static Count from(final long count) { + return new Count(count); } } @@ -180,4 +140,11 @@ private static List convertToTags(final RunnerPost runnerPost) { .map(runnerPostTag -> runnerPostTag.getTag().getTagName().getValue()) .toList(); } + + private static List convertToTags(final RunnerPost runnerPost, final List runnerPostTags) { + return runnerPostTags.stream() + .filter(runnerPostTag -> Objects.equals(runnerPostTag.getRunnerPost().getId(), runnerPost.getId())) + .map(runnerPostTag -> runnerPostTag.getTag().getTagName().getValue()) + .toList(); + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterRunnerPostResponse.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/SupporterRunnerPostResponse.java similarity index 88% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterRunnerPostResponse.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/SupporterRunnerPostResponse.java index 0edf5f814..22f270c5c 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterRunnerPostResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/SupporterRunnerPostResponse.java @@ -1,7 +1,7 @@ -package touch.baton.domain.runnerpost.controller.response; +package touch.baton.domain.runnerpost.query.controller.response; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; import java.util.List; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterRunnerPostResponses.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/SupporterRunnerPostResponses.java similarity index 81% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterRunnerPostResponses.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/SupporterRunnerPostResponses.java index 0365ac646..c84d4894a 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/controller/response/SupporterRunnerPostResponses.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/controller/response/SupporterRunnerPostResponses.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.controller.response; +package touch.baton.domain.runnerpost.query.controller.response; import java.util.List; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java new file mode 100644 index 000000000..d0ac63181 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java @@ -0,0 +1,123 @@ +package touch.baton.domain.runnerpost.query.repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.vo.TagReducedName; + +import java.util.List; + +import static touch.baton.domain.member.command.QMember.member; +import static touch.baton.domain.member.command.QRunner.runner; +import static touch.baton.domain.member.command.QSupporter.supporter; +import static touch.baton.domain.member.command.QSupporterRunnerPost.supporterRunnerPost; +import static touch.baton.domain.runnerpost.command.QRunnerPost.runnerPost; +import static touch.baton.domain.tag.command.QRunnerPostTag.runnerPostTag; +import static touch.baton.domain.tag.command.QTag.tag; + +@RequiredArgsConstructor +@Repository +public class RunnerPostPageRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List pageByReviewStatusAndTagReducedName(final Long previousLastId, + final int limit, + final TagReducedName tagReducedName, + final ReviewStatus reviewStatus + ) { + final JPAQuery query = jpaQueryFactory.selectFrom(runnerPost) + .join(runnerPost.runner, runner).fetchJoin() + .join(runner.member, member).fetchJoin() + .where(previousLastIdLt(previousLastId), reviewStatusEq(reviewStatus)) + .orderBy(runnerPost.id.desc()) + .limit(limit); + + if (tagReducedName != null) { + query.leftJoin(runnerPost.runnerPostTags.runnerPostTags, runnerPostTag) + .leftJoin(runnerPostTag.tag, tag) + .where(tag.tagReducedName.eq(tagReducedName)); + } + + return query.fetch(); + } + + public List pageBySupporterIdAndReviewStatus(final Long previousLastId, + final int limit, + final Long supporterId, + final ReviewStatus reviewStatus + ) { + return jpaQueryFactory.selectFrom(runnerPost) + .join(runnerPost.runner, runner).fetchJoin() + .join(runner.member, member).fetchJoin() + .join(runnerPost.supporter, supporter).fetchJoin() + .where(previousLastIdLt(previousLastId), supporterIdEq(supporterId), reviewStatusEq(reviewStatus)) + .orderBy(runnerPost.id.desc()) + .limit(limit) + .fetch(); + } + + public List pageBySupporterIdAndReviewStatusNotStarted(final Long previousLastId, final int limit, final Long supporterId) { + return jpaQueryFactory.select(supporterRunnerPost.runnerPost) + .from(supporterRunnerPost) + .join(supporterRunnerPost.runnerPost, runnerPost) + .join(runnerPost.runner, runner).fetchJoin() + .join(runner.member, member).fetchJoin() + .where(previousLastIdLt(previousLastId), supporterIdEqInSupporterRunnerPost(supporterId), reviewStatusEq(ReviewStatus.NOT_STARTED)) + .orderBy(runnerPost.id.desc()) + .limit(limit) + .fetch(); + } + + public List pageByRunnerIdAndReviewStatus(final Long previousLastId, + final int limit, + final Long runnerId, + final ReviewStatus reviewStatus + ) { + return jpaQueryFactory.selectFrom(runnerPost) + .join(runnerPost.runner, runner).fetchJoin() + .join(runner.member, member).fetchJoin() + .where(previousLastIdLt(previousLastId), runnerIdEq(runnerId), reviewStatusEq(reviewStatus)) + .orderBy(runnerPost.id.desc()) + .limit(limit) + .fetch(); + } + + private BooleanExpression previousLastIdLt(final Long previousLastId) { + if (previousLastId == null) { + return null; + } + return runnerPost.id.lt(previousLastId); + } + + private BooleanExpression reviewStatusEq(final ReviewStatus reviewStatus) { + if (reviewStatus == null) { + return null; + } + return runnerPost.reviewStatus.eq(reviewStatus); + } + + private BooleanExpression supporterIdEq(final Long supporterId) { + return runnerPost.supporter.id.eq(supporterId); + } + + private BooleanExpression runnerIdEq(final Long runnerId) { + return runnerPost.runner.id.eq(runnerId); + } + + private BooleanExpression supporterIdEqInSupporterRunnerPost(final Long supporterId) { + return supporterRunnerPost.supporter.id.eq(supporterId); + } + + public List findRunnerPostTagsByRunnerPosts(final List runnerPosts) { + return jpaQueryFactory.selectFrom(runnerPostTag) + .join(runnerPostTag.tag, tag).fetchJoin() + .where(runnerPostTag.runnerPost.in(runnerPosts)) + .fetch(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java new file mode 100644 index 000000000..962577ca5 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepository.java @@ -0,0 +1,57 @@ +package touch.baton.domain.runnerpost.query.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.repository.dto.RunnerPostApplicantCountDto; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; + +import java.util.List; +import java.util.Optional; + +public interface RunnerPostQueryRepository extends JpaRepository { + + @Query(value = """ + select rp, r, m + from RunnerPost rp + join fetch Runner r on r.id = rp.runner.id + join fetch Member m on m.id = r.member.id + where rp.id = :runnerPostId + """) + Optional joinMemberByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); + + @Query(""" + select rp + from RunnerPost rp + join fetch Supporter s on s.id = rp.supporter.id + join fetch Member m on m.id = s.member.id + where rp.id = :runnerPostId + """) + Optional joinSupporterByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); + + List findByRunnerId(final Long runnerId); + + @Query(""" + select new touch.baton.domain.runnerpost.command.repository.dto.RunnerPostApplicantCountDto(rp.id, coalesce(count(srp.id), 0L)) + from RunnerPost rp + left join SupporterRunnerPost srp on srp.runnerPost.id = rp.id + where rp.id in :runnerPostIds + group by rp.id + """) + List countApplicantsByRunnerPostIds(@Param("runnerPostIds") final List runnerPostIds); + + @Query(""" + select count(1) + from RunnerPost rp + where rp.runner.id = :runnerId and rp.reviewStatus = :reviewStatus + """) + long countByRunnerIdAndReviewStatus(@Param("runnerId") final Long runnerId, @Param("reviewStatus") final ReviewStatus reviewStatus); + + @Query(""" + select count(1) + from RunnerPost rp + where rp.supporter.id = :supporterId and rp.reviewStatus = :reviewStatus + """) + long countBySupporterIdAndReviewStatus(@Param("supporterId") final Long supporterId, @Param("reviewStatus") final ReviewStatus reviewStatus); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/dto/ApplicantCountMappingDto.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/dto/ApplicantCountMappingDto.java similarity index 79% rename from backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/dto/ApplicantCountMappingDto.java rename to backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/dto/ApplicantCountMappingDto.java index 64d26021b..172684823 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/dto/ApplicantCountMappingDto.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/dto/ApplicantCountMappingDto.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.repository.dto; +package touch.baton.domain.runnerpost.query.repository.dto; import java.util.Map; diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java new file mode 100644 index 000000000..e711fea85 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java @@ -0,0 +1,136 @@ +package touch.baton.domain.runnerpost.query.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.common.request.PageParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.member.query.repository.SupporterRunnerPostQueryRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.RunnerPostsApplicantCount; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.repository.dto.RunnerPostApplicantCountDto; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; +import touch.baton.domain.runnerpost.query.repository.RunnerPostPageRepository; +import touch.baton.domain.runnerpost.query.repository.RunnerPostQueryRepository; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.domain.tag.query.repository.RunnerPostTagQueryRepository; + +import java.util.List; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class RunnerPostQueryService { + + private final RunnerPostQueryRepository runnerPostQueryRepository; + private final RunnerPostPageRepository runnerPostPageRepository; + private final RunnerPostTagQueryRepository runnerPostTagQueryRepository; + private final SupporterRunnerPostQueryRepository supporterRunnerPostQueryRepository; + + public RunnerPost readByRunnerPostId(final Long runnerPostId) { + runnerPostTagQueryRepository.joinTagByRunnerPostId(runnerPostId); + return runnerPostQueryRepository.joinMemberByRunnerPostId(runnerPostId) + .orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다.")); + } + + public PageResponse pageRunnerPostByTagNameAndReviewStatus(final String tagName, + final PageParams pageParams, + final ReviewStatus reviewStatus + ) { + final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(pageParams.cursor(), pageParams.getLimitForQuery(), TagReducedName.nullableInstance(tagName), reviewStatus); + final List runnerPostTags = runnerPostPageRepository.findRunnerPostTagsByRunnerPosts(runnerPosts); + final RunnerPostsApplicantCount runnerPostsApplicantCount = readRunnerPostsApplicantCount(runnerPosts); + final List responses = runnerPosts.stream() + .map(runnerPost -> RunnerPostResponse.Simple.of(runnerPost, runnerPostsApplicantCount.getApplicantCountById(runnerPost.getId()), runnerPostTags)) + .toList(); + return PageResponse.of(responses, pageParams); + } + + public PageResponse pageRunnerPostBySupporterIdAndReviewStatus(final PageParams pageParams, + final Long supporterId, + final ReviewStatus reviewStatus + ) { + final List runnerPosts = pageRunnerPostFromSupporterByReviewStatus(pageParams, supporterId, reviewStatus); + final List runnerPostTags = runnerPostPageRepository.findRunnerPostTagsByRunnerPosts(runnerPosts); + final RunnerPostsApplicantCount runnerPostsApplicantCount = readRunnerPostsApplicantCount(runnerPosts); + final List responses = runnerPosts.stream() + .map(runnerPost -> RunnerPostResponse.Simple.of(runnerPost, runnerPostsApplicantCount.getApplicantCountById(runnerPost.getId()), runnerPostTags)) + .toList(); + return PageResponse.of(responses, pageParams); + } + + private List pageRunnerPostFromSupporterByReviewStatus(final PageParams pageParams, + final Long supporterId, + final ReviewStatus reviewStatus + ) { + if (reviewStatus == ReviewStatus.NOT_STARTED) { + return runnerPostPageRepository.pageBySupporterIdAndReviewStatusNotStarted(pageParams.cursor(), pageParams.getLimitForQuery(), supporterId); + } + return runnerPostPageRepository.pageBySupporterIdAndReviewStatus(pageParams.cursor(), pageParams.getLimitForQuery(), supporterId, reviewStatus); + } + + private RunnerPostsApplicantCount readRunnerPostsApplicantCount(final List runnerPosts) { + final List runnerPostIds = runnerPosts.stream() + .map(RunnerPost::getId) + .toList(); + final List runnerPostApplicantCountDtos = runnerPostQueryRepository.countApplicantsByRunnerPostIds(runnerPostIds); + return RunnerPostsApplicantCount.from(runnerPostApplicantCountDtos); + } + + public PageResponse pageRunnerPostByRunnerIdAndReviewStatus(final PageParams pageParams, + final Long runnerId, + final ReviewStatus reviewStatus + ) { + final List runnerPosts = runnerPostPageRepository.pageByRunnerIdAndReviewStatus(pageParams.cursor(), pageParams.getLimitForQuery(), runnerId, reviewStatus); + final List runnerPostTags = runnerPostPageRepository.findRunnerPostTagsByRunnerPosts(runnerPosts); + final RunnerPostsApplicantCount runnerPostsApplicantCount = readRunnerPostsApplicantCount(runnerPosts); + final List responses = runnerPosts.stream() + .map(runnerPost -> RunnerPostResponse.SimpleByRunner.of(runnerPost, runnerPostsApplicantCount.getApplicantCountById(runnerPost.getId()), runnerPostTags)) + .toList(); + return PageResponse.of(responses, pageParams); + } + + public List readSupporterRunnerPostsByRunnerPostId(final Runner runner, final Long runnerPostId) { + final RunnerPost foundRunnerPost = runnerPostQueryRepository.joinMemberByRunnerPostId(runnerPostId) + .orElseThrow(() -> new RunnerPostBusinessException(("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다."))); + + if (foundRunnerPost.isNotOwner(runner)) { + throw new RunnerPostBusinessException("RunnerPost 의 작성자가 아닙니다."); + } + + return supporterRunnerPostQueryRepository.readByRunnerPostId(runnerPostId); + } + + public List readRunnerPostsByRunnerId(final Long runnerId) { + return runnerPostQueryRepository.findByRunnerId(runnerId); + } + + public boolean existsRunnerPostApplicantByRunnerPostIdAndMemberId(final Long runnerPostId, final Long memberId) { + return supporterRunnerPostQueryRepository.existsByRunnerPostIdAndMemberId(runnerPostId, memberId); + } + + public long countApplicantsByRunnerPostId(final Long runnerPostId) { + return supporterRunnerPostQueryRepository.countByRunnerPostId(runnerPostId).orElse(0L); + } + + public long countRunnerPostByRunnerIdAndReviewStatus(final Long runnerId, final ReviewStatus reviewStatus) { + return runnerPostQueryRepository.countByRunnerIdAndReviewStatus(runnerId, reviewStatus); + } + + public long countRunnerPostBySupporterIdAndReviewStatus(final Long supporterId, final ReviewStatus reviewStatus) { + if (reviewStatus.isNotStarted()) { + return supporterRunnerPostQueryRepository.countRunnerPostBySupporterIdByReviewStatusNotStarted(supporterId); + } + return runnerPostQueryRepository.countBySupporterIdAndReviewStatus(supporterId, reviewStatus); + } + + @Transactional + public void increaseWatchedCount(final RunnerPost runnerPost) { + runnerPost.increaseWatchedCount(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepository.java deleted file mode 100644 index 734b105df..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepository.java +++ /dev/null @@ -1,54 +0,0 @@ -package touch.baton.domain.runnerpost.repository; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountDto; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountMappingDto; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.tag.vo.TagReducedName; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public interface RunnerPostReadRepository extends JpaRepository { - - default ApplicantCountMappingDto findApplicantCountMappingByRunnerPostIds(final List runnerPostIds) { - final Map applicantCountMapping = countApplicantsByRunnerPostIds(runnerPostIds) - .stream() - .collect(Collectors.toMap( - ApplicantCountDto::runnerPostId, - ApplicantCountDto::applicantCount - )); - - return new ApplicantCountMappingDto(applicantCountMapping); - } - - @Query(value = """ - select new touch.baton.domain.runnerpost.repository.dto.ApplicantCountDto(rp.id, count(srp.id)) - from RunnerPost rp - left outer join fetch SupporterRunnerPost srp on srp.runnerPost.id = rp.id - where rp.id in :runnerPostIds - group by rp.id - """) - List countApplicantsByRunnerPostIds(@Param("runnerPostIds") final List runnerPostIds); - - @Query(""" - select rp - from RunnerPost rp - join fetch Runner r on r.id = rp.runner.id - join fetch Member m on m.id = r.member.id - join fetch RunnerPostTag rpt on rpt.runnerPost.id = rp.id - where rpt.tag.tagReducedName = :tagReducedName - and rp.reviewStatus = :reviewStatus - """) - Page findByTagReducedNameAndReviewStatus( - final Pageable pageable, - @Param("tagReducedName") final TagReducedName tagReducedName, - @Param("reviewStatus") final ReviewStatus reviewStatus); - -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostRepository.java deleted file mode 100644 index e033a8ac1..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/RunnerPostRepository.java +++ /dev/null @@ -1,60 +0,0 @@ -package touch.baton.domain.runnerpost.repository; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.ReviewStatus; - -import java.util.List; -import java.util.Optional; - -public interface RunnerPostRepository extends JpaRepository { - - @Query(value = """ - select rp, r, m - from RunnerPost rp - join fetch Runner r on r.id = rp.runner.id - join fetch Member m on m.id = r.member.id - where rp.id = :runnerPostId - """) - Optional joinMemberByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); - - Page findByReviewStatus(final Pageable pageable, final ReviewStatus reviewStatus); - - @Query(countQuery = """ - select count(1) - from RunnerPost rp - where rp.runner.id = :runnerId - and rp.reviewStatus = :reviewStatus - """) - Page findByRunnerIdAndReviewStatus(final Pageable pageable, - @Param("runnerId") final Long runnerId, - @Param("reviewStatus") final ReviewStatus reviewStatus); - - List findByRunnerId(final Long runnerId); - - @Query(countQuery = """ - select count(1) - from RunnerPost rp - where rp.supporter.id = :supporterId - and rp.reviewStatus = :reviewStatus - """) - Page findBySupporterIdAndReviewStatus(final Pageable pageable, - @Param("supporterId") final Long supporterId, - @Param("reviewStatus") final ReviewStatus reviewStatus); - - @Query(""" - select rp - from RunnerPost rp - join fetch SupporterRunnerPost srp on srp.runnerPost.id = rp.id - where srp.supporter.id = :supporterId - and rp.reviewStatus = :reviewStatus - """) - Page joinSupporterRunnerPostBySupporterIdAndReviewStatus( - final Pageable pageable, - @Param("supporterId") final Long supporterId, - @Param("reviewStatus") final ReviewStatus reviewStatus); -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/dto/ApplicantCountDto.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/dto/ApplicantCountDto.java deleted file mode 100644 index 3191616c7..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/repository/dto/ApplicantCountDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package touch.baton.domain.runnerpost.repository.dto; - -public record ApplicantCountDto(Long runnerPostId, Long applicantCount) { -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostReadService.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostReadService.java deleted file mode 100644 index 13d6a27d1..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostReadService.java +++ /dev/null @@ -1,35 +0,0 @@ -package touch.baton.domain.runnerpost.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.RunnerPostReadRepository; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountMappingDto; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.tag.vo.TagReducedName; - -import java.util.List; - -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Service -public class RunnerPostReadService { - - private final RunnerPostReadRepository runnerPostReadRepository; - - public Page readRunnerPostByTagNameAndReviewStatus(final Pageable pageable, - final String tagName, - final ReviewStatus reviewStatus - ) { - final TagReducedName tagReducedName = TagReducedName.from(tagName); - - return runnerPostReadRepository.findByTagReducedNameAndReviewStatus(pageable, tagReducedName, reviewStatus); - } - - public ApplicantCountMappingDto readApplicantCountMappingByRunnerPostIds(final List runnerPostIds) { - return runnerPostReadRepository.findApplicantCountMappingByRunnerPostIds(runnerPostIds); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java deleted file mode 100644 index 5acbf99d3..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/RunnerPostService.java +++ /dev/null @@ -1,235 +0,0 @@ -package touch.baton.domain.runnerpost.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.exception.RunnerPostBusinessException; -import touch.baton.domain.runnerpost.repository.RunnerPostRepository; -import touch.baton.domain.runnerpost.service.dto.RunnerPostApplicantCreateRequest; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; -import touch.baton.domain.runnerpost.service.dto.RunnerPostUpdateRequest; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; -import touch.baton.domain.supporter.repository.SupporterRepository; -import touch.baton.domain.supporter.repository.SupporterRunnerPostRepository; -import touch.baton.domain.supporter.vo.Message; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.repository.RunnerPostTagRepository; -import touch.baton.domain.tag.repository.TagRepository; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Service -public class RunnerPostService { - - private final RunnerPostRepository runnerPostRepository; - private final RunnerPostTagRepository runnerPostTagRepository; - private final TagRepository tagRepository; - private final SupporterRepository supporterRepository; - private final SupporterRunnerPostRepository supporterRunnerPostRepository; - - @Transactional - public Long createRunnerPost(final Runner runner, final RunnerPostCreateRequest request) { - final RunnerPost runnerPost = toDomain(runner, request); - runnerPostRepository.save(runnerPost); - - final List tags = findTagsAfterSave(request.tags()); - - final List runnerPostTags = tags.stream() - .map(tag -> RunnerPostTag.builder() - .tag(tag) - .runnerPost(runnerPost).build()) - .toList(); - - runnerPost.addAllRunnerPostTags(runnerPostTags); - return runnerPost.getId(); - } - - private RunnerPost toDomain(final Runner runner, final RunnerPostCreateRequest request) { - return RunnerPost.newInstance(request.title(), - request.implementedContents(), - request.curiousContents(), - request.postscriptContents(), - request.pullRequestUrl(), - request.deadline(), - runner); - } - - private List findTagsAfterSave(final List tagNames) { - final List tags = new ArrayList<>(); - for (String tagName : tagNames) { - tagRepository.findByTagName(new TagName(tagName)) - .ifPresentOrElse(tags::add, addTagAfterSave(tags, tagName)); - } - - return tags; - } - - private Runnable addTagAfterSave(final List tags, final String tagName) { - return () -> { - final Tag savedTag = tagRepository.save(Tag.newInstance(tagName)); - tags.add(savedTag); - }; - } - - public RunnerPost readByRunnerPostId(final Long runnerPostId) { - runnerPostTagRepository.joinTagByRunnerPostId(runnerPostId); - return runnerPostRepository.joinMemberByRunnerPostId(runnerPostId) - .orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다.")); - } - - public List readSupporterRunnerPostsByRunnerPostId(final Runner runner, final Long runnerPostId) { - final RunnerPost foundRunnerPost = runnerPostRepository.joinMemberByRunnerPostId(runnerPostId) - .orElseThrow(() -> new RunnerPostBusinessException(("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다."))); - - if (foundRunnerPost.isNotOwner(runner)) { - throw new RunnerPostBusinessException("RunnerPost 의 작성자가 아닙니다."); - } - - return supporterRunnerPostRepository.readByRunnerPostId(runnerPostId); - } - - @Transactional - public void increaseWatchedCount(final RunnerPost runnerPost) { - runnerPost.increaseWatchedCount(); - } - - @Transactional - public void deleteByRunnerPostId(final Long runnerPostId, final Runner runner) { - final RunnerPost runnerPost = runnerPostRepository.findById(runnerPostId) - .orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 삭제할 러너 게시글이 존재하지 않습니다.")); - if (runnerPost.isNotOwner(runner)) { - throw new RunnerPostBusinessException("RunnerPost 를 게시한 유저가 아닙니다."); - } - if (runnerPost.isReviewStatusStarted()) { - throw new RunnerPostBusinessException("삭제할 수 없는 상태의 리뷰 상태입니다."); - } - if (supporterRunnerPostRepository.existsByRunnerPostId(runnerPostId)) { - throw new RunnerPostBusinessException("지원자가 존재하여 삭제할 수 없습니다."); - } - runnerPostRepository.deleteById(runnerPostId); - } - - private RunnerPost getRunnerPostOrThrowException(final Long runnerPostId) { - return runnerPostRepository.findById(runnerPostId) - .orElseThrow(() -> new RunnerPostBusinessException("해당 runnerPostId 로 러너 게시글을 찾을 수 없습니다. runnerPostId 를 다시 확인해주세요")); - } - - @Transactional - public Long createRunnerPostApplicant(final Supporter supporter, - final RunnerPostApplicantCreateRequest request, - final Long runnerPostId - ) { - final RunnerPost foundRunnerPost = getRunnerPostOrThrowException(runnerPostId); - final boolean isApplicantHistoryExist = supporterRunnerPostRepository.existsByRunnerPostIdAndSupporterId(runnerPostId, supporter.getId()); - if (isApplicantHistoryExist) { - throw new RunnerPostBusinessException("Supporter 는 이미 해당 RunnerPost 에 리뷰 신청을 한 이력이 있습니다."); - } - - final SupporterRunnerPost runnerPostApplicant = SupporterRunnerPost.builder() - .supporter(supporter) - .runnerPost(foundRunnerPost) - .message(new Message(request.message())) - .build(); - - return supporterRunnerPostRepository.save(runnerPostApplicant).getId(); - } - - public Page readRunnerPostsByReviewStatus(final Pageable pageable, final ReviewStatus reviewStatus) { - return runnerPostRepository.findByReviewStatus(pageable, reviewStatus); - } - - public List readRunnerPostsByRunnerId(final Long runnerId) { - return runnerPostRepository.findByRunnerId(runnerId); - } - - public Page readRunnerPostsByRunnerIdAndReviewStatus(final Pageable pageable, - final Long runnerId, - final ReviewStatus reviewStatus - ) { - return runnerPostRepository.findByRunnerIdAndReviewStatus(pageable, runnerId, reviewStatus); - } - - public Page readRunnerPostsBySupporterIdAndReviewStatus(final Pageable pageable, - final Long supporterId, - final ReviewStatus reviewStatus - ) { - if (reviewStatus.isSameAsNotStarted()) { - return runnerPostRepository.joinSupporterRunnerPostBySupporterIdAndReviewStatus(pageable, supporterId, reviewStatus); - } - return runnerPostRepository.findBySupporterIdAndReviewStatus(pageable, supporterId, reviewStatus); - } - - public List readCountsByRunnerPostIds(final List runnerPostIds) { - return supporterRunnerPostRepository.countByRunnerPostIds(runnerPostIds); - } - - @Transactional - public void updateRunnerPostReviewStatusDone(final Long runnerPostId, final Supporter supporter) { - final RunnerPost foundRunnerPost = runnerPostRepository.findById(runnerPostId) - .orElseThrow(() -> new RunnerPostBusinessException("해당 식별자의 러너 게시글이 존재하지 않습니다.")); - - if (Objects.isNull(foundRunnerPost.getSupporter())) { - throw new RunnerPostBusinessException("아직 서포터가 배정이 안 된 게시글 입니다."); - } - - if (foundRunnerPost.isDifferentSupporter(supporter)) { - throw new RunnerPostBusinessException("다른 사람이 리뷰 중인 게시글의 상태를 변경할 수 없습니다."); - } - - foundRunnerPost.finishReview(); - } - - public long readCountByRunnerPostId(final Long runnerPostId) { - return supporterRunnerPostRepository.countByRunnerPostId(runnerPostId).orElse(0L); - } - - @Transactional - public void deleteSupporterRunnerPost(final Supporter supporter, final Long runnerPostId) { - final RunnerPost runnerPost = runnerPostRepository.findById(runnerPostId) - .orElseThrow(() -> new RunnerPostBusinessException("존재하지 않는 RunnerPost 입니다.")); - if (!runnerPost.isReviewStatusNotStarted()) { - throw new RunnerPostBusinessException("이미 진행 중인 러너 게시글의 서포터 지원은 철회할 수 없습니다."); - } - supporterRunnerPostRepository.deleteBySupporterIdAndRunnerPostId(supporter.getId(), runnerPostId); - } - - @Transactional - public void updateRunnerPostAppliedSupporter(final Runner runner, - final Long runnerPostId, - final RunnerPostUpdateRequest.SelectSupporter request - ) { - final Supporter foundApplySupporter = supporterRepository.findById(request.supporterId()) - .orElseThrow(() -> new RunnerPostBusinessException("해당하는 식별자값의 서포터를 찾을 수 없습니다.")); - final RunnerPost foundRunnerPost = runnerPostRepository.findById(runnerPostId) - .orElseThrow(() -> new RunnerPostBusinessException("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다.")); - - if (isApplySupporter(runnerPostId, foundApplySupporter)) { - throw new RunnerPostBusinessException("게시글에 리뷰를 제안한 서포터가 아닙니다."); - } - if (foundRunnerPost.isNotOwner(runner)) { - throw new RunnerPostBusinessException("RunnerPost 의 글쓴이와 다른 사용자입니다."); - } - - foundRunnerPost.assignSupporter(foundApplySupporter); - } - - private boolean isApplySupporter(final Long runnerPostId, final Supporter foundSupporter) { - return !supporterRunnerPostRepository.existsByRunnerPostIdAndSupporterId(runnerPostId, foundSupporter.getId()); - } - - public boolean existsRunnerPostApplicantByRunnerPostIdAndMemberId(final Long runnerPostId, final Long memberId) { - return supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId(runnerPostId, memberId); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostCreateTestRequest.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostCreateTestRequest.java deleted file mode 100644 index 2ccf42d03..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/service/dto/RunnerPostCreateTestRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package touch.baton.domain.runnerpost.service.dto; - -import java.time.LocalDateTime; -import java.util.List; - -public record RunnerPostCreateTestRequest(String title, - List tags, - String pullRequestUrl, - LocalDateTime deadline, - String contents, - Long supporterId -) { -} diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterController.java b/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterController.java deleted file mode 100644 index 2de7b66df..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterController.java +++ /dev/null @@ -1,29 +0,0 @@ -package touch.baton.domain.supporter.controller; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import touch.baton.domain.supporter.controller.response.SupporterReadResponses; -import touch.baton.domain.supporter.controller.response.SupporterResponse; -import touch.baton.domain.supporter.service.SupporterService; - -import java.util.List; - -@RequiredArgsConstructor -@RequestMapping("/api/v1/supporters") -@RestController -public class SupporterController { - - private final SupporterService supporterService; - - @GetMapping("/test") - public ResponseEntity readAll() { - final List responses = supporterService.readAllSupporters().stream() - .map(SupporterResponse.Detail::from) - .toList(); - - return ResponseEntity.ok(SupporterReadResponses.NoFiltering.from(responses)); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java b/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java deleted file mode 100644 index 0e9719334..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/SupporterProfileController.java +++ /dev/null @@ -1,50 +0,0 @@ -package touch.baton.domain.supporter.controller; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -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.util.UriComponentsBuilder; -import touch.baton.domain.oauth.controller.resolver.AuthSupporterPrincipal; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.controller.response.SupporterResponse; -import touch.baton.domain.supporter.service.SupporterService; -import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; - -import java.net.URI; - -@RequiredArgsConstructor -@RequestMapping("/api/v1/profile/supporter") -@RestController -public class SupporterProfileController { - - private final SupporterService supporterService; - - @GetMapping("/{supporterId}") - public ResponseEntity readProfileBySupporterId(@PathVariable final Long supporterId) { - final Supporter foundSupporter = supporterService.readBySupporterId(supporterId); - final SupporterResponse.Profile response = SupporterResponse.Profile.from(foundSupporter); - - return ResponseEntity.ok(response); - } - - @GetMapping("/me") - public ResponseEntity readSupporterMyProfileByLoginToken(@AuthSupporterPrincipal final Supporter loginedSupporter) { - final SupporterResponse.MyProfile response = SupporterResponse.MyProfile.from(loginedSupporter); - - return ResponseEntity.ok(response); - } - - @PatchMapping("/me") - public ResponseEntity updateProfile(@AuthSupporterPrincipal final Supporter supporter, - @RequestBody @Valid final SupporterUpdateRequest request) { - supporterService.updateSupporter(supporter, request); - final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/profile/supporter/me").build().toUri(); - return ResponseEntity.noContent().location(redirectUri).build(); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/response/SupporterReadResponses.java b/backend/baton/src/main/java/touch/baton/domain/supporter/controller/response/SupporterReadResponses.java deleted file mode 100644 index b27231463..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/controller/response/SupporterReadResponses.java +++ /dev/null @@ -1,13 +0,0 @@ -package touch.baton.domain.supporter.controller.response; - -import java.util.List; - -public record SupporterReadResponses() { - - public record NoFiltering(List data) { - - public static NoFiltering from(final List data) { - return new SupporterReadResponses.NoFiltering(data); - } - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java b/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java deleted file mode 100644 index 8d8cd1363..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepository.java +++ /dev/null @@ -1,59 +0,0 @@ -package touch.baton.domain.supporter.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import touch.baton.domain.supporter.SupporterRunnerPost; - -import java.util.List; -import java.util.Optional; - -public interface SupporterRunnerPostRepository extends JpaRepository { - - @Query(""" - select count(1) - from SupporterRunnerPost srp - group by srp.runnerPost.id - having srp.runnerPost.id in (:runnerPostIds) - """) - List countByRunnerPostIdIn(@Param("runnerPostIds") final List runnerPostIds); - - @Query(""" - select count(1) - from SupporterRunnerPost srp - group by srp.runnerPost.id - having srp.runnerPost.id = :runnerPostId - """) - Optional countByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); - - @Query(""" - select case when exists ( - select 1 from SupporterRunnerPost srp - where srp.runnerPost.id = rp.id) - then ( - select count(srp.id) from SupporterRunnerPost srp - where srp.runnerPost.id = rp.id - ) else 0 end - from RunnerPost rp - where rp.id in :runnerPostIds - order by rp.id desc - """) - List countByRunnerPostIds(@Param("runnerPostIds") final List runnerPostIds); - - @Query(""" - select (count(1) >= 1) - from SupporterRunnerPost srp - join fetch Member m on m.id = srp.supporter.member.id - where srp.runnerPost.id = :runnerPostId - and srp.supporter.member.id = :memberId - """) - boolean existsByRunnerPostIdAndMemberId(@Param("runnerPostId") final Long runnerPostId, @Param("memberId") final Long memberId); - - boolean existsByRunnerPostId(final Long runnerPostId); - - void deleteBySupporterIdAndRunnerPostId(final Long supporterId, final Long runnerPostId); - - boolean existsByRunnerPostIdAndSupporterId(final Long runnerPostId, final Long supporterId); - - List readByRunnerPostId(final Long runnerPostId); -} diff --git a/backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java b/backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java deleted file mode 100644 index 935e6b53b..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/supporter/service/SupporterService.java +++ /dev/null @@ -1,67 +0,0 @@ -package touch.baton.domain.supporter.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.exception.SupporterBusinessException; -import touch.baton.domain.supporter.repository.SupporterRepository; -import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; -import touch.baton.domain.technicaltag.TechnicalTag; -import touch.baton.domain.technicaltag.repository.SupporterTechnicalTagRepository; -import touch.baton.domain.technicaltag.repository.TechnicalTagRepository; - -import java.util.List; -import java.util.Optional; - -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Service -public class SupporterService { - - private final SupporterRepository supporterRepository; - private final TechnicalTagRepository technicalTagRepository; - private final SupporterTechnicalTagRepository supporterTechnicalTagRepository; - - public List readAllSupporters() { - return supporterRepository.findAll(); - } - - public Supporter readBySupporterId(final Long supporterId) { - return supporterRepository.joinMemberBySupporterId(supporterId) - .orElseThrow(() -> new SupporterBusinessException("존재하지 않는 서포터 식별자값으로 조회할 수 없습니다.")); - } - - @Transactional - public void updateSupporter(final Supporter supporter, final SupporterUpdateRequest supporterUpdateRequest) { - supporter.updateMemberName(new MemberName(supporterUpdateRequest.name())); - supporter.updateCompany(new Company(supporterUpdateRequest.company())); - supporter.updateIntroduction(new Introduction(supporterUpdateRequest.introduction())); - supporterTechnicalTagRepository.deleteBySupporter(supporter); - supporterUpdateRequest.technicalTags() - .forEach(tagName -> createSupporterTechnicalTag(supporter, new TagName(tagName))); - } - - private SupporterTechnicalTag createSupporterTechnicalTag(final Supporter supporter, final TagName tagName) { - final TechnicalTag technicalTag = findTechnicalTagIfExistElseCreate(tagName); - return supporterTechnicalTagRepository.save(SupporterTechnicalTag.builder() - .supporter(supporter) - .technicalTag(technicalTag) - .build() - ); - } - - private TechnicalTag findTechnicalTagIfExistElseCreate(final TagName tagName) { - final Optional maybeTechnicalTag = technicalTagRepository.findByTagName(tagName); - return maybeTechnicalTag.orElseGet(() -> - technicalTagRepository.save(TechnicalTag.builder() - .tagName(tagName) - .build() - )); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/RunnerPostTag.java b/backend/baton/src/main/java/touch/baton/domain/tag/command/RunnerPostTag.java similarity index 95% rename from backend/baton/src/main/java/touch/baton/domain/tag/RunnerPostTag.java rename to backend/baton/src/main/java/touch/baton/domain/tag/command/RunnerPostTag.java index a496e7571..43372823a 100644 --- a/backend/baton/src/main/java/touch/baton/domain/tag/RunnerPostTag.java +++ b/backend/baton/src/main/java/touch/baton/domain/tag/command/RunnerPostTag.java @@ -1,4 +1,4 @@ -package touch.baton.domain.tag; +package touch.baton.domain.tag.command; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; @@ -9,7 +9,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import touch.baton.domain.runnerpost.RunnerPost; +import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.domain.tag.exception.RunnerPostTagDomainException; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/RunnerPostTags.java b/backend/baton/src/main/java/touch/baton/domain/tag/command/RunnerPostTags.java similarity index 97% rename from backend/baton/src/main/java/touch/baton/domain/tag/RunnerPostTags.java rename to backend/baton/src/main/java/touch/baton/domain/tag/command/RunnerPostTags.java index abadc38c1..8d97a9272 100644 --- a/backend/baton/src/main/java/touch/baton/domain/tag/RunnerPostTags.java +++ b/backend/baton/src/main/java/touch/baton/domain/tag/command/RunnerPostTags.java @@ -1,4 +1,4 @@ -package touch.baton.domain.tag; +package touch.baton.domain.tag.command; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/Tag.java b/backend/baton/src/main/java/touch/baton/domain/tag/command/Tag.java similarity index 93% rename from backend/baton/src/main/java/touch/baton/domain/tag/Tag.java rename to backend/baton/src/main/java/touch/baton/domain/tag/command/Tag.java index bc15db73a..bcdd5cc63 100644 --- a/backend/baton/src/main/java/touch/baton/domain/tag/Tag.java +++ b/backend/baton/src/main/java/touch/baton/domain/tag/command/Tag.java @@ -1,6 +1,5 @@ -package touch.baton.domain.tag; +package touch.baton.domain.tag.command; -import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -9,8 +8,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.tag.command.vo.TagReducedName; import touch.baton.domain.tag.exception.TagDomainException; -import touch.baton.domain.tag.vo.TagReducedName; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/command/repository/TagCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/tag/command/repository/TagCommandRepository.java new file mode 100644 index 000000000..187d4b72a --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/tag/command/repository/TagCommandRepository.java @@ -0,0 +1,12 @@ +package touch.baton.domain.tag.command.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.tag.command.Tag; + +import java.util.Optional; + +public interface TagCommandRepository extends JpaRepository { + + Optional findByTagName(final TagName tagName); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/vo/TagReducedName.java b/backend/baton/src/main/java/touch/baton/domain/tag/command/vo/TagReducedName.java similarity index 83% rename from backend/baton/src/main/java/touch/baton/domain/tag/vo/TagReducedName.java rename to backend/baton/src/main/java/touch/baton/domain/tag/command/vo/TagReducedName.java index acf7f1074..dfb09ae77 100644 --- a/backend/baton/src/main/java/touch/baton/domain/tag/vo/TagReducedName.java +++ b/backend/baton/src/main/java/touch/baton/domain/tag/command/vo/TagReducedName.java @@ -1,4 +1,4 @@ -package touch.baton.domain.tag.vo; +package touch.baton.domain.tag.command.vo; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -26,6 +26,13 @@ private TagReducedName(final String value) { this.value = value; } + public static TagReducedName nullableInstance(final String notReducedValue) { + if (notReducedValue == null) { + return null; + } + return new TagReducedName(reduceName(notReducedValue)); + } + public static TagReducedName from(final String notReducedValue) { validateNotNull(notReducedValue); final String reducedValue = reduceName(notReducedValue); diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/controller/TagController.java b/backend/baton/src/main/java/touch/baton/domain/tag/controller/TagController.java deleted file mode 100644 index 2aa05d1c0..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/tag/controller/TagController.java +++ /dev/null @@ -1,36 +0,0 @@ -package touch.baton.domain.tag.controller; - -import jakarta.annotation.Nullable; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import touch.baton.domain.tag.controller.response.TagSearchResponse; -import touch.baton.domain.tag.controller.response.TagSearchResponses; -import touch.baton.domain.tag.service.TagService; - -import java.util.Collections; -import java.util.List; - -@RequiredArgsConstructor -@RequestMapping("/api/v1/tags") -@RestController -public class TagController { - - private final TagService tagService; - - @GetMapping("/search") - public ResponseEntity readTagsByTagName(@Nullable @RequestParam(required = false) final String tagName) { - if (tagName == null || tagName.isBlank()) { - return ResponseEntity.ok().body(TagSearchResponses.Detail.from(Collections.emptyList())); - } - - final List tagSearchResponses = tagService.readTagsByReducedName(tagName).stream() - .map(TagSearchResponse.TagResponse::from) - .toList(); - - return ResponseEntity.ok(TagSearchResponses.Detail.from(tagSearchResponses)); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/controller/response/TagSearchResponses.java b/backend/baton/src/main/java/touch/baton/domain/tag/controller/response/TagSearchResponses.java deleted file mode 100644 index 6ff10f2ca..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/tag/controller/response/TagSearchResponses.java +++ /dev/null @@ -1,12 +0,0 @@ -package touch.baton.domain.tag.controller.response; - -import java.util.List; - -public record TagSearchResponses() { - - public record Detail(List data) { - public static Detail from(final List data) { - return new Detail(data); - } - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/TagQueryController.java b/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/TagQueryController.java new file mode 100644 index 000000000..d94506f47 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/TagQueryController.java @@ -0,0 +1,31 @@ +package touch.baton.domain.tag.query.controller; + +import jakarta.annotation.Nullable; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.domain.tag.query.controller.response.TagSearchResponses; +import touch.baton.domain.tag.query.service.TagQueryService; + +import java.util.List; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/tags") +@RestController +public class TagQueryController { + + private final TagQueryService tagQueryService; + + @GetMapping("/search") + public ResponseEntity readTagsByTagName(@Nullable @RequestParam(required = false) final String tagName) { + final TagReducedName tagReducedName = TagReducedName.nullableInstance(tagName); + final List foundTags = tagQueryService.readTagsByReducedName(tagReducedName, 10); + + return ResponseEntity.ok(TagSearchResponses.Detail.from(foundTags)); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/controller/response/TagSearchResponse.java b/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/response/TagSearchResponse.java similarity index 71% rename from backend/baton/src/main/java/touch/baton/domain/tag/controller/response/TagSearchResponse.java rename to backend/baton/src/main/java/touch/baton/domain/tag/query/controller/response/TagSearchResponse.java index f91aa19dc..c06017a2c 100644 --- a/backend/baton/src/main/java/touch/baton/domain/tag/controller/response/TagSearchResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/response/TagSearchResponse.java @@ -1,6 +1,6 @@ -package touch.baton.domain.tag.controller.response; +package touch.baton.domain.tag.query.controller.response; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.tag.command.Tag; public record TagSearchResponse() { diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/response/TagSearchResponses.java b/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/response/TagSearchResponses.java new file mode 100644 index 000000000..5bd9be9ba --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/tag/query/controller/response/TagSearchResponses.java @@ -0,0 +1,19 @@ +package touch.baton.domain.tag.query.controller.response; + +import touch.baton.domain.tag.command.Tag; + +import java.util.List; + +public record TagSearchResponses() { + + public record Detail(List data) { + + public static Detail from(final List tags) { + final List response = tags.stream() + .map(TagSearchResponse.TagResponse::from) + .toList(); + + return new Detail(response); + } + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/repository/RunnerPostTagRepository.java b/backend/baton/src/main/java/touch/baton/domain/tag/query/repository/RunnerPostTagQueryRepository.java similarity index 71% rename from backend/baton/src/main/java/touch/baton/domain/tag/repository/RunnerPostTagRepository.java rename to backend/baton/src/main/java/touch/baton/domain/tag/query/repository/RunnerPostTagQueryRepository.java index a72bd070a..bcf258a6c 100644 --- a/backend/baton/src/main/java/touch/baton/domain/tag/repository/RunnerPostTagRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/tag/query/repository/RunnerPostTagQueryRepository.java @@ -1,13 +1,13 @@ -package touch.baton.domain.tag.repository; +package touch.baton.domain.tag.query.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.tag.RunnerPostTag; +import touch.baton.domain.tag.command.RunnerPostTag; import java.util.List; -public interface RunnerPostTagRepository extends JpaRepository { +public interface RunnerPostTagQueryRepository extends JpaRepository { @Query(""" select rpt diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/query/repository/TagQuerydslRepository.java b/backend/baton/src/main/java/touch/baton/domain/tag/query/repository/TagQuerydslRepository.java new file mode 100644 index 000000000..d1e74cd8b --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/tag/query/repository/TagQuerydslRepository.java @@ -0,0 +1,26 @@ +package touch.baton.domain.tag.query.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; + +import java.util.List; + +import static touch.baton.domain.tag.command.QTag.tag; + +@RequiredArgsConstructor +@Repository +public class TagQuerydslRepository { + + private final JPAQueryFactory queryFactory; + + public List findByTagReducedName(final TagReducedName tagReducedName, final int limit) { + return queryFactory.selectFrom(tag) + .where(tag.tagReducedName.value.startsWith(tagReducedName.getValue())) + .orderBy(tag.tagReducedName.value.asc(), tag.tagName.value.desc()) + .limit(limit) + .fetch(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/query/service/TagQueryService.java b/backend/baton/src/main/java/touch/baton/domain/tag/query/service/TagQueryService.java new file mode 100644 index 000000000..1fcee1ae1 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/tag/query/service/TagQueryService.java @@ -0,0 +1,27 @@ +package touch.baton.domain.tag.query.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.domain.tag.query.repository.TagQuerydslRepository; + +import java.util.Collections; +import java.util.List; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class TagQueryService { + + private final TagQuerydslRepository tagQuerydslRepository; + + public List readTagsByReducedName(final TagReducedName tagReducedName, final int limit) { + if (tagReducedName == null || tagReducedName.getValue().isBlank()) { + return Collections.emptyList(); + } + + return tagQuerydslRepository.findByTagReducedName(tagReducedName, limit); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/repository/TagRepository.java b/backend/baton/src/main/java/touch/baton/domain/tag/repository/TagRepository.java deleted file mode 100644 index 2ed88778e..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/tag/repository/TagRepository.java +++ /dev/null @@ -1,26 +0,0 @@ -package touch.baton.domain.tag.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.vo.TagReducedName; - -import java.util.List; -import java.util.Optional; - -public interface TagRepository extends JpaRepository { - - Optional findByTagName(final TagName tagName); - - @Query(""" - select t - from Tag t - where t.tagReducedName like :tagReducedName% - order by t.tagReducedName asc - limit 10 - """) - List readTagsByReducedName(@Param("tagReducedName")TagReducedName tagReducedName); - -} diff --git a/backend/baton/src/main/java/touch/baton/domain/tag/service/TagService.java b/backend/baton/src/main/java/touch/baton/domain/tag/service/TagService.java deleted file mode 100644 index 2b6a2163b..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/tag/service/TagService.java +++ /dev/null @@ -1,24 +0,0 @@ -package touch.baton.domain.tag.service; - -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.repository.TagRepository; -import touch.baton.domain.tag.vo.TagReducedName; - -import java.util.List; - -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Service -public class TagService { - - private final TagRepository tagRepository; - - public List readTagsByReducedName(final String tagName) { - final String reducedName = TagReducedName.from(tagName).getValue(); - - return tagRepository.readTagsByReducedName(TagReducedName.from(tagName)); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/RunnerTechnicalTag.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/RunnerTechnicalTag.java similarity index 95% rename from backend/baton/src/main/java/touch/baton/domain/technicaltag/RunnerTechnicalTag.java rename to backend/baton/src/main/java/touch/baton/domain/technicaltag/command/RunnerTechnicalTag.java index c0800bddf..8cb877e24 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/RunnerTechnicalTag.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/RunnerTechnicalTag.java @@ -1,4 +1,4 @@ -package touch.baton.domain.technicaltag; +package touch.baton.domain.technicaltag.command; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; @@ -9,7 +9,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import touch.baton.domain.runner.Runner; +import touch.baton.domain.member.command.Runner; import touch.baton.domain.tag.exception.SupporterTechnicalTagDomainException; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/RunnerTechnicalTags.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/RunnerTechnicalTags.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/technicaltag/RunnerTechnicalTags.java rename to backend/baton/src/main/java/touch/baton/domain/technicaltag/command/RunnerTechnicalTags.java index f76ab5c65..6cac0afd6 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/RunnerTechnicalTags.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/RunnerTechnicalTags.java @@ -1,4 +1,4 @@ -package touch.baton.domain.technicaltag; +package touch.baton.domain.technicaltag.command; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/SupporterTechnicalTag.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/SupporterTechnicalTag.java similarity index 95% rename from backend/baton/src/main/java/touch/baton/domain/technicaltag/SupporterTechnicalTag.java rename to backend/baton/src/main/java/touch/baton/domain/technicaltag/command/SupporterTechnicalTag.java index 645252353..c19748a9c 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/SupporterTechnicalTag.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/SupporterTechnicalTag.java @@ -1,4 +1,4 @@ -package touch.baton.domain.technicaltag; +package touch.baton.domain.technicaltag.command; import jakarta.persistence.Entity; import jakarta.persistence.ForeignKey; @@ -9,7 +9,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Supporter; import touch.baton.domain.tag.exception.SupporterTechnicalTagDomainException; import java.util.Objects; diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/SupporterTechnicalTags.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/SupporterTechnicalTags.java similarity index 94% rename from backend/baton/src/main/java/touch/baton/domain/technicaltag/SupporterTechnicalTags.java rename to backend/baton/src/main/java/touch/baton/domain/technicaltag/command/SupporterTechnicalTags.java index 6b9e72ca2..f0ee7b598 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/SupporterTechnicalTags.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/SupporterTechnicalTags.java @@ -1,4 +1,4 @@ -package touch.baton.domain.technicaltag; +package touch.baton.domain.technicaltag.command; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/TechnicalTag.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/TechnicalTag.java similarity index 95% rename from backend/baton/src/main/java/touch/baton/domain/technicaltag/TechnicalTag.java rename to backend/baton/src/main/java/touch/baton/domain/technicaltag/command/TechnicalTag.java index cffffce11..a7b10f2af 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/TechnicalTag.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/TechnicalTag.java @@ -1,4 +1,4 @@ -package touch.baton.domain.technicaltag; +package touch.baton.domain.technicaltag.command; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/RunnerTechnicalTagRepository.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/repository/RunnerTechnicalTagCommandRepository.java similarity index 61% rename from backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/RunnerTechnicalTagRepository.java rename to backend/baton/src/main/java/touch/baton/domain/technicaltag/command/repository/RunnerTechnicalTagCommandRepository.java index 717f40479..e50420098 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/RunnerTechnicalTagRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/repository/RunnerTechnicalTagCommandRepository.java @@ -1,13 +1,13 @@ -package touch.baton.domain.technicaltag.repository; +package touch.baton.domain.technicaltag.command.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.technicaltag.RunnerTechnicalTag; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTag; -public interface RunnerTechnicalTagRepository extends JpaRepository { +public interface RunnerTechnicalTagCommandRepository extends JpaRepository { @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM RunnerTechnicalTag rt WHERE rt.runner = :runner") diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepository.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/repository/SupporterTechnicalTagCommandRepository.java similarity index 61% rename from backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepository.java rename to backend/baton/src/main/java/touch/baton/domain/technicaltag/command/repository/SupporterTechnicalTagCommandRepository.java index ace8e6c7f..325415b66 100644 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/command/repository/SupporterTechnicalTagCommandRepository.java @@ -1,13 +1,13 @@ -package touch.baton.domain.technicaltag.repository; +package touch.baton.domain.technicaltag.command.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; -public interface SupporterTechnicalTagRepository extends JpaRepository { +public interface SupporterTechnicalTagCommandRepository extends JpaRepository { @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("DELETE FROM SupporterTechnicalTag st WHERE st.supporter = :supporter") diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/query/repository/TechnicalTagQueryRepository.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/query/repository/TechnicalTagQueryRepository.java new file mode 100644 index 000000000..7550fb99e --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/technicaltag/query/repository/TechnicalTagQueryRepository.java @@ -0,0 +1,12 @@ +package touch.baton.domain.technicaltag.query.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.technicaltag.command.TechnicalTag; + +import java.util.Optional; + +public interface TechnicalTagQueryRepository extends JpaRepository { + + Optional findByTagName(final TagName tagName); +} diff --git a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java b/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java deleted file mode 100644 index 681c35827..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package touch.baton.domain.technicaltag.repository; - -import org.springframework.data.jpa.repository.JpaRepository; -import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.technicaltag.TechnicalTag; - -import java.util.Optional; - -public interface TechnicalTagRepository extends JpaRepository { - - Optional findByTagName(final TagName tagName); -} diff --git a/backend/baton/src/main/java/touch/baton/infra/auth/jwt/JwtDecoder.java b/backend/baton/src/main/java/touch/baton/infra/auth/jwt/JwtDecoder.java index ab1870e8a..01076ede6 100644 --- a/backend/baton/src/main/java/touch/baton/infra/auth/jwt/JwtDecoder.java +++ b/backend/baton/src/main/java/touch/baton/infra/auth/jwt/JwtDecoder.java @@ -12,8 +12,8 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.oauth.AuthorizationHeader; -import touch.baton.domain.oauth.exception.OauthRequestException; +import touch.baton.domain.oauth.command.AuthorizationHeader; +import touch.baton.domain.oauth.command.exception.OauthRequestException; @Profile("!test") @RequiredArgsConstructor diff --git a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/authcode/GithubAuthCodeRequestUrlProvider.java b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/authcode/GithubAuthCodeRequestUrlProvider.java index 1fed7b572..b443bca4b 100644 --- a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/authcode/GithubAuthCodeRequestUrlProvider.java +++ b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/authcode/GithubAuthCodeRequestUrlProvider.java @@ -3,8 +3,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.authcode.AuthCodeRequestUrlProvider; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.authcode.AuthCodeRequestUrlProvider; import touch.baton.infra.auth.oauth.github.GithubOauthConfig; @RequiredArgsConstructor diff --git a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/client/GithubInformationClient.java b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/client/GithubInformationClient.java index 664d90d21..4e9f47a71 100644 --- a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/client/GithubInformationClient.java +++ b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/client/GithubInformationClient.java @@ -2,9 +2,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import touch.baton.domain.oauth.OauthInformation; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.client.OauthInformationClient; +import touch.baton.domain.oauth.command.OauthInformation; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.client.OauthInformationClient; import touch.baton.infra.auth.oauth.github.GithubOauthConfig; import touch.baton.infra.auth.oauth.github.http.GithubHttpInterface; import touch.baton.infra.auth.oauth.github.request.GithubTokenRequest; diff --git a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java index 9a3db4030..739411625 100644 --- a/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java +++ b/backend/baton/src/main/java/touch/baton/infra/auth/oauth/github/response/GithubMemberResponse.java @@ -2,13 +2,13 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import jakarta.annotation.Nullable; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.OauthInformation; -import touch.baton.domain.oauth.token.SocialToken; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.OauthInformation; +import touch.baton.domain.oauth.command.token.SocialToken; import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; diff --git a/backend/baton/src/main/java/touch/baton/infra/github/GithubBranchManager.java b/backend/baton/src/main/java/touch/baton/infra/github/GithubBranchManager.java index 8d86a1066..9b894fe93 100644 --- a/backend/baton/src/main/java/touch/baton/infra/github/GithubBranchManager.java +++ b/backend/baton/src/main/java/touch/baton/infra/github/GithubBranchManager.java @@ -13,7 +13,7 @@ import org.springframework.web.client.RestTemplate; import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.common.exception.ClientRequestException; -import touch.baton.domain.member.service.dto.GithubBranchManageable; +import touch.baton.domain.member.command.service.GithubBranchManageable; import touch.baton.infra.exception.InfraException; import touch.baton.infra.github.request.CreateBranchRequest; import touch.baton.infra.github.response.ReadBranchInfoResponse; diff --git a/backend/baton/src/main/resources/application.yml b/backend/baton/src/main/resources/application.yml index ddb45ab4b..c99aff939 100644 --- a/backend/baton/src/main/resources/application.yml +++ b/backend/baton/src/main/resources/application.yml @@ -5,6 +5,7 @@ spring: jpa: properties: hibernate: + default_batch_fetch_size: 50 format_sql: true hibernate: ddl-auto: validate diff --git a/backend/baton/src/main/resources/db/migration/V20231007_1__create_table_notification.sql b/backend/baton/src/main/resources/db/migration/V20231007_1__create_table_notification.sql new file mode 100644 index 000000000..1015c6727 --- /dev/null +++ b/backend/baton/src/main/resources/db/migration/V20231007_1__create_table_notification.sql @@ -0,0 +1,13 @@ +CREATE TABLE notification +( + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + message VARCHAR(255) NOT NULL, + notification_type VARCHAR(255) NOT NULL, + is_read bit default false NOT NULL, + referenced_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL, + deleted_at DATETIME(6) +); diff --git a/backend/baton/src/main/resources/db/migration/V20231007_2__alter_table_notification_constraint_fk.sql b/backend/baton/src/main/resources/db/migration/V20231007_2__alter_table_notification_constraint_fk.sql new file mode 100644 index 000000000..eb7cf8c22 --- /dev/null +++ b/backend/baton/src/main/resources/db/migration/V20231007_2__alter_table_notification_constraint_fk.sql @@ -0,0 +1,3 @@ +ALTER TABLE notification + ADD CONSTRAINT fk_notification_to_member + FOREIGN KEY (member_id) REFERENCES member (id); diff --git a/backend/baton/src/main/resources/db/migration/V20231011__create_review_status_index_on_runner_post.sql b/backend/baton/src/main/resources/db/migration/V20231011__create_review_status_index_on_runner_post.sql new file mode 100644 index 000000000..05fbc5f41 --- /dev/null +++ b/backend/baton/src/main/resources/db/migration/V20231011__create_review_status_index_on_runner_post.sql @@ -0,0 +1,2 @@ +create index idx_runner_post_review_status on runner_post (review_status); + diff --git a/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java index ef53e2a55..edcfe5ec3 100644 --- a/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/common/AssuredSupport.java @@ -144,6 +144,25 @@ public static ExtractableResponse get(final String uri, final String a .extract(); } + public static ExtractableResponse patch(final String uri) { + return RestAssured + .given().log().ifValidationFails() + .when().log().ifValidationFails() + .patch(uri) + .then().log().ifError() + .extract(); + } + + public static ExtractableResponse patch(final String uri, final String accessToken) { + return RestAssured + .given().log().ifValidationFails() + .auth().preemptive().oauth2(accessToken) + .when().log().ifValidationFails() + .patch(uri) + .then().log().ifError() + .extract(); + } + public static ExtractableResponse patch(final String uri, final String accessToken, final PathParams pathParams) { return RestAssured .given().log().ifValidationFails() diff --git a/backend/baton/src/test/java/touch/baton/assure/common/JwtTestManager.java b/backend/baton/src/test/java/touch/baton/assure/common/JwtTestManager.java index 906c71dfa..ef620da04 100644 --- a/backend/baton/src/test/java/touch/baton/assure/common/JwtTestManager.java +++ b/backend/baton/src/test/java/touch/baton/assure/common/JwtTestManager.java @@ -3,7 +3,7 @@ import io.jsonwebtoken.Claims; import org.springframework.boot.test.context.TestComponent; import org.springframework.context.annotation.Profile; -import touch.baton.domain.member.vo.SocialId; +import touch.baton.domain.member.command.vo.SocialId; import touch.baton.infra.auth.jwt.JwtDecoder; import static touch.baton.fixture.vo.AuthorizationHeaderFixture.bearerAuthorizationHeader; diff --git a/backend/baton/src/test/java/touch/baton/assure/common/OauthLoginTestManager.java b/backend/baton/src/test/java/touch/baton/assure/common/OauthLoginTestManager.java index 2c6309136..c4d2a627a 100644 --- a/backend/baton/src/test/java/touch/baton/assure/common/OauthLoginTestManager.java +++ b/backend/baton/src/test/java/touch/baton/assure/common/OauthLoginTestManager.java @@ -1,8 +1,9 @@ package touch.baton.assure.common; import touch.baton.assure.oauth.OauthAssuredSupport; -import touch.baton.domain.oauth.OauthType; +import touch.baton.domain.oauth.command.OauthType; +@SuppressWarnings("NonAsciiCharacters") public class OauthLoginTestManager { public String 소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(final String 테스트용_사용자_MockAuthCode) { diff --git a/backend/baton/src/test/java/touch/baton/assure/feedback/SupporterFeedbackCreateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/feedback/command/SupporterFeedbackCreateAssuredTest.java similarity index 79% rename from backend/baton/src/test/java/touch/baton/assure/feedback/SupporterFeedbackCreateAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/feedback/command/SupporterFeedbackCreateAssuredTest.java index e5aaf8775..44a383f87 100644 --- a/backend/baton/src/test/java/touch/baton/assure/feedback/SupporterFeedbackCreateAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/feedback/command/SupporterFeedbackCreateAssuredTest.java @@ -1,22 +1,24 @@ -package touch.baton.assure.feedback; +package touch.baton.assure.feedback.command; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport; -import touch.baton.assure.runnerpost.RunnerPostAssuredSupport; +import touch.baton.assure.feedback.support.command.SupporterFeedbackCreateAssuredSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostUpdateSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.supporter.Supporter; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; import java.time.LocalDateTime; import java.util.List; import static org.springframework.http.HttpStatus.CREATED; -import static touch.baton.assure.feedback.SupporterFeedbackAssuredSupport.서포터_피드백_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.feedback.support.command.SupporterFeedbackCreateAssuredSupport.서포터_피드백_요청; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.클라이언트_요청; @SuppressWarnings("NonAsciiCharacters") class SupporterFeedbackCreateAssuredTest extends AssuredTestConfig { @@ -24,11 +26,11 @@ class SupporterFeedbackCreateAssuredTest extends AssuredTestConfig { @Test void 러너가_서포터_피드백을_등록한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when @@ -37,7 +39,7 @@ class SupporterFeedbackCreateAssuredTest extends AssuredTestConfig { 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); // then - SupporterFeedbackAssuredSupport + SupporterFeedbackCreateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(디투_액세스_토큰) .서포터_피드백을_등록한다( @@ -49,11 +51,10 @@ class SupporterFeedbackCreateAssuredTest extends AssuredTestConfig { } private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport + return RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( - 러너_게시글_생성_요청( + .러너_게시글_등록_요청한다(러너_게시글_생성_요청( "테스트용_러너_게시글_제목", List.of("자바", "스프링"), "https://test-pull-request.com", @@ -70,8 +71,7 @@ class SupporterFeedbackCreateAssuredTest extends AssuredTestConfig { } private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredCreateSupport - .클라이언트_요청() + 클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .서포터가_러너_게시글에_리뷰를_신청한다(디투_러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") @@ -80,7 +80,7 @@ class SupporterFeedbackCreateAssuredTest extends AssuredTestConfig { } private void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터_헤나, final String 디투_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport + RunnerPostUpdateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(디투_액세스_토큰) .러너가_서포터를_선택한다(디투_러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터_헤나.getId())) @@ -90,7 +90,7 @@ class SupporterFeedbackCreateAssuredTest extends AssuredTestConfig { } private void 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport + RunnerPostUpdateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(디투_러너_게시글_식별자값) diff --git a/backend/baton/src/test/java/touch/baton/assure/feedback/SupporterFeedbackAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/feedback/support/command/SupporterFeedbackCreateAssuredSupport.java similarity index 66% rename from backend/baton/src/test/java/touch/baton/assure/feedback/SupporterFeedbackAssuredSupport.java rename to backend/baton/src/test/java/touch/baton/assure/feedback/support/command/SupporterFeedbackCreateAssuredSupport.java index c1e029fc6..311dc83ff 100644 --- a/backend/baton/src/test/java/touch/baton/assure/feedback/SupporterFeedbackAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/feedback/support/command/SupporterFeedbackCreateAssuredSupport.java @@ -1,24 +1,24 @@ -package touch.baton.assure.feedback; +package touch.baton.assure.feedback.support.command; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import org.springframework.http.HttpHeaders; import touch.baton.assure.common.AssuredSupport; import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.domain.feedback.service.SupporterFeedBackCreateRequest; +import touch.baton.domain.feedback.command.service.dto.SupporterFeedBackCreateRequest; import java.util.List; import static org.assertj.core.api.SoftAssertions.assertSoftly; @SuppressWarnings("NonAsciiCharacters") -public class SupporterFeedbackAssuredSupport { +public class SupporterFeedbackCreateAssuredSupport { - private SupporterFeedbackAssuredSupport() { + private SupporterFeedbackCreateAssuredSupport() { } - public static SupporterFeedbackClientRequestBuilder 클라이언트_요청() { - return new SupporterFeedbackClientRequestBuilder(); + public static SupporterFeedbackCreateBuilder 클라이언트_요청() { + return new SupporterFeedbackCreateBuilder(); } public static SupporterFeedBackCreateRequest 서포터_피드백_요청(final String 리뷰_타입, @@ -29,32 +29,32 @@ private SupporterFeedbackAssuredSupport() { return new SupporterFeedBackCreateRequest(리뷰_타입, 디스크립션, 서포터_식별자값, 러너_게시글_식별자값); } - public static class SupporterFeedbackClientRequestBuilder { + public static class SupporterFeedbackCreateBuilder { private ExtractableResponse response; private String accessToken; - public SupporterFeedbackClientRequestBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + public SupporterFeedbackCreateBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { this.accessToken = 액세스_토큰; return this; } - public SupporterFeedbackClientRequestBuilder 서포터_피드백을_등록한다(final SupporterFeedBackCreateRequest 서포터_피드백_정보) { + public SupporterFeedbackCreateBuilder 서포터_피드백을_등록한다(final SupporterFeedBackCreateRequest 서포터_피드백_정보) { response = AssuredSupport.post("/api/v1/feedback/supporter", accessToken, 서포터_피드백_정보); return this; } - public SupporterFeedbackServerResponseBuilder 서버_응답() { - return new SupporterFeedbackServerResponseBuilder(response); + public SupporterFeedbackCreateResponseBuilder 서버_응답() { + return new SupporterFeedbackCreateResponseBuilder(response); } } - public static class SupporterFeedbackServerResponseBuilder { + public static class SupporterFeedbackCreateResponseBuilder { private final ExtractableResponse response; - public SupporterFeedbackServerResponseBuilder(final ExtractableResponse response) { + public SupporterFeedbackCreateResponseBuilder(final ExtractableResponse response) { this.response = response; } diff --git a/backend/baton/src/test/java/touch/baton/assure/member/MemberBranchCreateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/member/command/MemberBranchAssuredTest.java similarity index 70% rename from backend/baton/src/test/java/touch/baton/assure/member/MemberBranchCreateAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/member/command/MemberBranchAssuredTest.java index 0f1d19700..bea28873d 100644 --- a/backend/baton/src/test/java/touch/baton/assure/member/MemberBranchCreateAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/command/MemberBranchAssuredTest.java @@ -1,22 +1,23 @@ -package touch.baton.assure.member; +package touch.baton.assure.member.command; import org.junit.jupiter.api.Test; import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.member.support.command.MemberBranchCreateAssuredSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.service.dto.GithubRepoNameRequest; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.service.dto.GithubRepoNameRequest; import static org.springframework.http.HttpStatus.CREATED; @SuppressWarnings("NonAsciiCharacters") -class MemberBranchCreateAssuredTest extends AssuredTestConfig { +class MemberBranchAssuredTest extends AssuredTestConfig { @Test void 로그인_한_사용자가_요청한_레포의_브랜치를_생성한다() { - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final GithubRepoNameRequest 브랜치_생성_요청 = new GithubRepoNameRequest("ditoo"); - MemberBranchAssuredSupport + MemberBranchCreateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(디투_액세스_토큰) .입력받은_레포에_사용자_github_계정명으로_된_브랜치를_생성한다(브랜치_생성_요청) diff --git a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerUpdateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/member/command/RunnerUpdateAssuredTest.java similarity index 54% rename from backend/baton/src/test/java/touch/baton/assure/runner/RunnerUpdateAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/member/command/RunnerUpdateAssuredTest.java index de7cc7755..63d351b8d 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerUpdateAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/command/RunnerUpdateAssuredTest.java @@ -1,19 +1,21 @@ -package touch.baton.assure.runner; +package touch.baton.assure.member.command; import org.junit.jupiter.api.Test; import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.member.support.command.RunnerUpdateAssuredSupport; +import touch.baton.assure.member.support.query.RunnerQueryAssuredSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.runner.service.dto.RunnerUpdateRequest; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.service.dto.RunnerUpdateRequest; +import touch.baton.domain.member.command.vo.SocialId; import java.util.List; import static org.springframework.http.HttpStatus.NO_CONTENT; -import static touch.baton.assure.runner.RunnerAssuredSupport.러너_본인_프로필_수정_요청; -import static touch.baton.domain.common.exception.ClientErrorCode.COMPANY_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.NAME_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.RUNNER_INTRODUCTION_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.RUNNER_TECHNICAL_TAGS_ARE_NULL; +import static touch.baton.assure.member.support.command.RunnerUpdateAssuredSupport.러너_본인_프로필_수정_요청; +import static touch.baton.assure.member.support.query.RunnerQueryAssuredSupport.RunnerQueryResponseBuilder.러너_프로필_상세_응답; +import static touch.baton.domain.common.exception.ClientErrorCode.*; @SuppressWarnings("NonAsciiCharacters") class RunnerUpdateAssuredTest extends AssuredTestConfig { @@ -21,12 +23,12 @@ class RunnerUpdateAssuredTest extends AssuredTestConfig { @Test void 러너_정보를_수정한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); - final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = 러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", "수정된_러너_소개글", List.of("자바", "스프링")); + final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = RunnerUpdateAssuredSupport.러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", "수정된_러너_소개글", List.of("자바", "스프링")); // when, then - RunnerAssuredSupport + RunnerUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_본인_프로필을_수정한다(러너_본인_프로필_수정_요청) @@ -38,12 +40,12 @@ class RunnerUpdateAssuredTest extends AssuredTestConfig { @Test void 러너_정보_수정_시에_이름이_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); - final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = 러너_본인_프로필_수정_요청(null, "수정된_회사", "수정된_러너_소개글", List.of("자바", "스프링")); + final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = RunnerUpdateAssuredSupport.러너_본인_프로필_수정_요청(null, "수정된_회사", "수정된_러너_소개글", List.of("자바", "스프링")); // when, then - RunnerAssuredSupport + RunnerUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_본인_프로필을_수정한다(러너_본인_프로필_수정_요청) @@ -55,11 +57,11 @@ class RunnerUpdateAssuredTest extends AssuredTestConfig { @Test void 서포터_정보_수정_시에_소속이_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = 러너_본인_프로필_수정_요청("수정된_헤나", null, "수정된_러너_소개글", List.of("자바", "스프링")); - RunnerAssuredSupport + final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = RunnerUpdateAssuredSupport.러너_본인_프로필_수정_요청("수정된_헤나", null, "수정된_러너_소개글", List.of("자바", "스프링")); + RunnerUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_본인_프로필을_수정한다(러너_본인_프로필_수정_요청) @@ -71,11 +73,11 @@ class RunnerUpdateAssuredTest extends AssuredTestConfig { @Test void 러너_정보_수정_시에_소개글이_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = 러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", null, List.of("자바", "스프링")); - RunnerAssuredSupport + final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = RunnerUpdateAssuredSupport.러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", null, List.of("자바", "스프링")); + RunnerUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_본인_프로필을_수정한다(러너_본인_프로필_수정_요청) @@ -87,11 +89,11 @@ class RunnerUpdateAssuredTest extends AssuredTestConfig { @Test void 러너_정보_수정_시에_기술_태그가_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = 러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", "수정된_러너_소개글", null); - RunnerAssuredSupport + final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = RunnerUpdateAssuredSupport.러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", "수정된_러너_소개글", null); + RunnerUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_본인_프로필을_수정한다(러너_본인_프로필_수정_요청) @@ -99,4 +101,31 @@ class RunnerUpdateAssuredTest extends AssuredTestConfig { .서버_응답() .러너_본인_프로필_수정_실패를_검증한다(RUNNER_TECHNICAL_TAGS_ARE_NULL); } + + @Test + void 수정된_러너_프로필_조회에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); + final Runner 러너_헤나 = runnerRepository.getBySocialId(헤나_소셜_아이디); + + final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = 러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", "수정된_러너_소개글", List.of("자바", "스프링")); + RunnerUpdateAssuredSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너_본인_프로필을_수정한다(러너_본인_프로필_수정_요청) + + .서버_응답() + .러너_본인_프로필_수정_성공을_검증한다(new HttpStatusAndLocationHeader(NO_CONTENT, "/api/v1/profile/runner/me")); + + // when, then + RunnerQueryAssuredSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너_프로필을_상세_조회한다(러너_헤나.getId()) + + .서버_응답() + .러너_프로필_상세_조회를_검증한다(러너_프로필_상세_응답(러너_헤나, 러너_본인_프로필_수정_요청)); + } } diff --git a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterUpdateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/member/command/SupporterUpdateAssuredTest.java similarity index 82% rename from backend/baton/src/test/java/touch/baton/assure/supporter/SupporterUpdateAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/member/command/SupporterUpdateAssuredTest.java index 0799a8cb9..ae9e393cd 100644 --- a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterUpdateAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/command/SupporterUpdateAssuredTest.java @@ -1,17 +1,15 @@ -package touch.baton.assure.supporter; +package touch.baton.assure.member.command; import org.junit.jupiter.api.Test; +import touch.baton.assure.member.support.command.SupporterUpdateAssuredSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; import java.util.List; import static org.springframework.http.HttpStatus.NO_CONTENT; -import static touch.baton.assure.supporter.SupporterAssuredSupport.서포터_본인_정보_수정_요청; -import static touch.baton.domain.common.exception.ClientErrorCode.COMPANY_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.NAME_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.SUPPORTER_INTRODUCTION_IS_NULL; -import static touch.baton.domain.common.exception.ClientErrorCode.SUPPORTER_TECHNICAL_TAGS_ARE_NULL; +import static touch.baton.assure.member.support.command.SupporterUpdateAssuredSupport.서포터_본인_정보_수정_요청; +import static touch.baton.domain.common.exception.ClientErrorCode.*; @SuppressWarnings("NonAsciiCharacters") class SupporterUpdateAssuredTest extends AssuredTestConfig { @@ -19,10 +17,10 @@ class SupporterUpdateAssuredTest extends AssuredTestConfig { @Test void 서포터_정보를_수정한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - SupporterAssuredSupport + SupporterUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .서포터_본인_프로필을_수정한다( @@ -36,10 +34,10 @@ class SupporterUpdateAssuredTest extends AssuredTestConfig { @Test void 서포터_정보_수정_시에_이름이_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - SupporterAssuredSupport + SupporterUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .서포터_본인_프로필을_수정한다( @@ -53,10 +51,10 @@ class SupporterUpdateAssuredTest extends AssuredTestConfig { @Test void 서포터_정보_수정_시에_소속이_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - SupporterAssuredSupport + SupporterUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .서포터_본인_프로필을_수정한다( @@ -70,10 +68,10 @@ class SupporterUpdateAssuredTest extends AssuredTestConfig { @Test void 서포터_정보_수정_시에_소개글이_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - SupporterAssuredSupport + SupporterUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .서포터_본인_프로필을_수정한다( @@ -87,10 +85,10 @@ class SupporterUpdateAssuredTest extends AssuredTestConfig { @Test void 서포터_정보_수정_시에_기술_태그가_없으면_예외가_발생한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when, then - SupporterAssuredSupport + SupporterUpdateAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .서포터_본인_프로필을_수정한다( diff --git a/backend/baton/src/test/java/touch/baton/assure/member/MemberReadWithLoginedMemberAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/member/query/MemberQueryAssuredTest.java similarity index 63% rename from backend/baton/src/test/java/touch/baton/assure/member/MemberReadWithLoginedMemberAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/member/query/MemberQueryAssuredTest.java index b6b06dc2c..dfa419f8f 100644 --- a/backend/baton/src/test/java/touch/baton/assure/member/MemberReadWithLoginedMemberAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/query/MemberQueryAssuredTest.java @@ -1,24 +1,25 @@ -package touch.baton.assure.member; +package touch.baton.assure.member.query; import org.junit.jupiter.api.Test; +import touch.baton.assure.member.support.query.MemberQueryAssuredSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.SocialId; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.SocialId; -import static touch.baton.assure.member.MemberAssuredSupport.로그인한_사용자_프로필_응답; +import static touch.baton.assure.member.support.query.MemberQueryAssuredSupport.로그인한_사용자_프로필_응답; @SuppressWarnings("NonAsciiCharacters") -class MemberReadWithLoginedMemberAssuredTest extends AssuredTestConfig { +class MemberQueryAssuredTest extends AssuredTestConfig { @Test void 로그인_한_사용자_프로필을_조회한다() { - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Member 사용자_헤나 = memberRepository.getBySocialId(헤나_소셜_아이디); - MemberAssuredSupport + MemberQueryAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .사용자_본인_프로필을_가지고_있는_액세스_토큰으로_조회한다() diff --git a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerReadWithLoginedRunnerAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/member/query/RunnerQueryAssuredTest.java similarity index 61% rename from backend/baton/src/test/java/touch/baton/assure/runner/RunnerReadWithLoginedRunnerAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/member/query/RunnerQueryAssuredTest.java index 74df5065e..f32c1ad02 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerReadWithLoginedRunnerAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/query/RunnerQueryAssuredTest.java @@ -1,33 +1,33 @@ -package touch.baton.assure.runner; +package touch.baton.assure.member.query; import org.junit.jupiter.api.Test; +import touch.baton.assure.member.support.command.RunnerUpdateAssuredSupport; +import touch.baton.assure.member.support.query.RunnerQueryAssuredSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.SocialId; import java.util.Collections; -import static touch.baton.assure.runner.RunnerAssuredSupport.러너_본인_프로필_응답; - @SuppressWarnings("NonAsciiCharacters") -class RunnerReadWithLoginedRunnerAssuredTest extends AssuredTestConfig { +class RunnerQueryAssuredTest extends AssuredTestConfig { @Test void 러너_본인_프로필을_가지고_있는_액세스_토큰으로_조회에_성공한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Runner 러너_헤나 = runnerRepository.getBySocialId(헤나_소셜_아이디); // when, then - RunnerAssuredSupport + RunnerQueryAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_본인_프로필을_가지고_있는_액세스_토큰으로_조회한다() .서버_응답() - .러너_본인_프로필_조회_성공을_검증한다(러너_본인_프로필_응답(러너_헤나, Collections.emptyList())); + .러너_본인_프로필_조회_성공을_검증한다(RunnerUpdateAssuredSupport.러너_본인_프로필_응답(러너_헤나, Collections.emptyList())); } } diff --git a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterReadBySupporterIdAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/member/query/SupporterQueryAssuredTest.java similarity index 70% rename from backend/baton/src/test/java/touch/baton/assure/supporter/SupporterReadBySupporterIdAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/member/query/SupporterQueryAssuredTest.java index 8d4ddad9a..d8018e307 100644 --- a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterReadBySupporterIdAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/query/SupporterQueryAssuredTest.java @@ -1,29 +1,30 @@ -package touch.baton.assure.supporter; +package touch.baton.assure.member.query; import org.junit.jupiter.api.Test; +import touch.baton.assure.member.support.query.SupporterQueryAssuredSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.supporter.Supporter; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; import java.util.Collections; -import static touch.baton.assure.supporter.SupporterAssuredSupport.서포터_MyProfile_응답; -import static touch.baton.assure.supporter.SupporterAssuredSupport.서포터_Profile_응답; +import static touch.baton.assure.member.support.query.SupporterQueryAssuredSupport.서포터_MyProfile_응답; +import static touch.baton.assure.member.support.query.SupporterQueryAssuredSupport.서포터_Profile_응답; @SuppressWarnings("NonAsciiCharacters") -class SupporterReadBySupporterIdAssuredTest extends AssuredTestConfig { +class SupporterQueryAssuredTest extends AssuredTestConfig { @Test void 서포터_프로필을_조회한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); // when, then - SupporterAssuredSupport + SupporterQueryAssuredSupport .클라이언트_요청() .서포터_프로필을_서포터_식별자값으로_조회한다(서포터_헤나.getId()) @@ -34,13 +35,13 @@ class SupporterReadBySupporterIdAssuredTest extends AssuredTestConfig { @Test void 서포터_마이페이지_프로필을_조회한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); // when, then - SupporterAssuredSupport + SupporterQueryAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .서포터_마이페이지를_액세스_토큰으로_조회한다() diff --git a/backend/baton/src/test/java/touch/baton/assure/member/MemberBranchAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/member/support/command/MemberBranchCreateAssuredSupport.java similarity index 52% rename from backend/baton/src/test/java/touch/baton/assure/member/MemberBranchAssuredSupport.java rename to backend/baton/src/test/java/touch/baton/assure/member/support/command/MemberBranchCreateAssuredSupport.java index 1cad5b149..916bbf729 100644 --- a/backend/baton/src/test/java/touch/baton/assure/member/MemberBranchAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/support/command/MemberBranchCreateAssuredSupport.java @@ -1,55 +1,50 @@ -package touch.baton.assure.member; +package touch.baton.assure.member.support.command; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import touch.baton.assure.common.AssuredSupport; import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.domain.member.service.dto.GithubRepoNameRequest; +import touch.baton.domain.member.command.service.dto.GithubRepoNameRequest; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.springframework.http.HttpHeaders.LOCATION; @SuppressWarnings("NonAsciiCharacters") -class MemberBranchAssuredSupport { +public class MemberBranchCreateAssuredSupport { - private MemberBranchAssuredSupport() { + private MemberBranchCreateAssuredSupport() { } - public static MemberClientRequestBuilder 클라이언트_요청() { - return new MemberClientRequestBuilder(); + public static MemberBranchCreateBuilder 클라이언트_요청() { + return new MemberBranchCreateBuilder(); } - public static class MemberClientRequestBuilder { + public static class MemberBranchCreateBuilder { private ExtractableResponse response; private String accessToken; - public MemberClientRequestBuilder 액세스_토큰으로_로그인_한다(final String 액세스_토큰) { + public MemberBranchCreateBuilder 액세스_토큰으로_로그인_한다(final String 액세스_토큰) { accessToken = 액세스_토큰; return this; } - public MemberClientRequestBuilder 사용자_본인_프로필을_가지고_있는_액세스_토큰으로_조회한다() { - response = AssuredSupport.get("api/v1/profile/me", accessToken); - return this; - } - - public MemberClientRequestBuilder 입력받은_레포에_사용자_github_계정명으로_된_브랜치를_생성한다(final GithubRepoNameRequest 레포_이름_요청) { + public MemberBranchCreateBuilder 입력받은_레포에_사용자_github_계정명으로_된_브랜치를_생성한다(final GithubRepoNameRequest 레포_이름_요청) { response = AssuredSupport.post("/api/v1/branch", accessToken, 레포_이름_요청); return this; } - public MemberServerResponseBuilder 서버_응답() { - return new MemberServerResponseBuilder(response); + public MemberBranchCreateResponseBuilder 서버_응답() { + return new MemberBranchCreateResponseBuilder(response); } } - public static class MemberServerResponseBuilder { + public static class MemberBranchCreateResponseBuilder { private final ExtractableResponse response; - public MemberServerResponseBuilder(final ExtractableResponse response) { + public MemberBranchCreateResponseBuilder(final ExtractableResponse response) { this.response = response; } diff --git a/backend/baton/src/test/java/touch/baton/assure/member/support/command/RunnerUpdateAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/member/support/command/RunnerUpdateAssuredSupport.java new file mode 100644 index 000000000..25c24a3cd --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/member/support/command/RunnerUpdateAssuredSupport.java @@ -0,0 +1,96 @@ +package touch.baton.assure.member.support.command; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpHeaders; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.domain.common.exception.ClientErrorCode; +import touch.baton.domain.common.response.ErrorResponse; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.service.dto.RunnerUpdateRequest; +import touch.baton.domain.member.query.controller.response.RunnerResponse; + +import java.util.List; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerUpdateAssuredSupport { + + private RunnerUpdateAssuredSupport() { + } + + public static RunnerUpdateBuilder 클라이언트_요청() { + return new RunnerUpdateBuilder(); + } + + public static RunnerResponse.Mine 러너_본인_프로필_응답(final Runner 러너, final List 러너_태그_목록) { + return new RunnerResponse.Mine( + 러너.getMember().getMemberName().getValue(), + 러너.getMember().getCompany().getValue(), + 러너.getMember().getImageUrl().getValue(), + 러너.getMember().getGithubUrl().getValue(), + 러너.getIntroduction().getValue(), + 러너_태그_목록 + ); + } + + public static RunnerUpdateRequest 러너_본인_프로필_수정_요청(final String 러너_이름, + final String 회사, + final String 러너_소개글, + final List 러너_기술태그_목록 + ) { + return new RunnerUpdateRequest(러너_이름, 회사, 러너_소개글, 러너_기술태그_목록); + } + + public static class RunnerUpdateBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerUpdateBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerUpdateBuilder 러너_본인_프로필을_수정한다(final RunnerUpdateRequest 러너_업데이트_요청) { + response = AssuredSupport.patch("/api/v1/profile/runner/me", accessToken, 러너_업데이트_요청); + return this; + } + + public RunnerUpdateResponseBuilder 서버_응답() { + return new RunnerUpdateResponseBuilder(response); + } + } + + public static class RunnerUpdateResponseBuilder { + + private final ExtractableResponse response; + + public RunnerUpdateResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public static RunnerUpdateBuilder 클라이언트_요청() { + return new RunnerUpdateBuilder(); + } + + public void 러너_본인_프로필_수정_성공을_검증한다(final HttpStatusAndLocationHeader 응답상태_및_로케이션) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(응답상태_및_로케이션.getHttpStatus().value()); + softly.assertThat(response.header(HttpHeaders.LOCATION)).contains(응답상태_및_로케이션.getLocation()); + }); + } + + public void 러너_본인_프로필_수정_실패를_검증한다(final ClientErrorCode 클라이언트_에러_코드) { + final ErrorResponse actual = this.response.as(ErrorResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(클라이언트_에러_코드.getHttpStatus().value()); + softly.assertThat(actual.errorCode()).isEqualTo(클라이언트_에러_코드.getErrorCode()); + }); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/member/support/command/SupporterUpdateAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/member/support/command/SupporterUpdateAssuredSupport.java new file mode 100644 index 000000000..26aa67ee0 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/member/support/command/SupporterUpdateAssuredSupport.java @@ -0,0 +1,78 @@ +package touch.baton.assure.member.support.command; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.domain.common.exception.ClientErrorCode; +import touch.baton.domain.common.response.ErrorResponse; +import touch.baton.domain.member.command.service.dto.SupporterUpdateRequest; + +import java.util.List; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class SupporterUpdateAssuredSupport { + + private SupporterUpdateAssuredSupport() { + } + + public static SupporterUpdateBuilder 클라이언트_요청() { + return new SupporterUpdateBuilder(); + } + + public static SupporterUpdateRequest 서포터_본인_정보_수정_요청(final String 이름, + final String 회사, + final String 서포터_자기소개글, + final List 서포터_기술_태그_목록 + ) { + return new SupporterUpdateRequest(이름, 회사, 서포터_자기소개글, 서포터_기술_태그_목록); + } + + public static class SupporterUpdateBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public SupporterUpdateBuilder 액세스_토큰으로_로그인_한다(final String 액세스_토큰) { + accessToken = 액세스_토큰; + return this; + } + + public SupporterUpdateBuilder 서포터_본인_프로필을_수정한다(final SupporterUpdateRequest 서포터_업데이트_요청) { + response = AssuredSupport.patch("/api/v1/profile/supporter/me", accessToken, 서포터_업데이트_요청); + return this; + } + + public SupporterUpdateResponseBuilder 서버_응답() { + return new SupporterUpdateResponseBuilder(response); + } + } + + public static class SupporterUpdateResponseBuilder { + + private final ExtractableResponse response; + + public SupporterUpdateResponseBuilder(final ExtractableResponse 응답) { + this.response = 응답; + } + + public void 서포터_본인_프로필_수정_성공을_검증한다(final HttpStatus HTTP_STATUS) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value()); + softly.assertThat(response.header("Location")).isNotNull(); + }); + } + + public void 서포터_본인_프로필_수정_실패를_검증한다(final ClientErrorCode 클라이언트_에러_코드) { + final ErrorResponse actual = this.response.as(ErrorResponse.class); + + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(클라이언트_에러_코드.getHttpStatus().value()); + softly.assertThat(actual.errorCode()).isEqualTo(클라이언트_에러_코드.getErrorCode()); + }); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/member/MemberAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/member/support/query/MemberQueryAssuredSupport.java similarity index 60% rename from backend/baton/src/test/java/touch/baton/assure/member/MemberAssuredSupport.java rename to backend/baton/src/test/java/touch/baton/assure/member/support/query/MemberQueryAssuredSupport.java index 48fe463eb..0303f85b5 100644 --- a/backend/baton/src/test/java/touch/baton/assure/member/MemberAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/support/query/MemberQueryAssuredSupport.java @@ -1,53 +1,53 @@ -package touch.baton.assure.member; +package touch.baton.assure.member.support.query; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import touch.baton.assure.common.AssuredSupport; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.controller.response.LoginMemberInfoResponse; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.controller.response.LoginMemberInfoResponse; import static org.assertj.core.api.SoftAssertions.assertSoftly; @SuppressWarnings("NonAsciiCharacters") -public class MemberAssuredSupport { +public class MemberQueryAssuredSupport { - private MemberAssuredSupport() { + private MemberQueryAssuredSupport() { } - public static MemberClientRequestBuilder 클라이언트_요청() { - return new MemberClientRequestBuilder(); + public static MemberQueryBuilder 클라이언트_요청() { + return new MemberQueryBuilder(); } public static LoginMemberInfoResponse 로그인한_사용자_프로필_응답(final Member 맴버) { return LoginMemberInfoResponse.from(맴버); } - public static class MemberClientRequestBuilder { + public static class MemberQueryBuilder { private ExtractableResponse response; private String accessToken; - public MemberClientRequestBuilder 액세스_토큰으로_로그인_한다(final String 액세스_토큰) { + public MemberQueryBuilder 액세스_토큰으로_로그인_한다(final String 액세스_토큰) { accessToken = 액세스_토큰; return this; } - public MemberClientRequestBuilder 사용자_본인_프로필을_가지고_있는_액세스_토큰으로_조회한다() { + public MemberQueryBuilder 사용자_본인_프로필을_가지고_있는_액세스_토큰으로_조회한다() { response = AssuredSupport.get("api/v1/profile/me", accessToken); return this; } - public MemberServerResponseBuilder 서버_응답() { - return new MemberServerResponseBuilder(response); + public MemberQueryResponseBuilder 서버_응답() { + return new MemberQueryResponseBuilder(response); } } - public static class MemberServerResponseBuilder { + public static class MemberQueryResponseBuilder { private final ExtractableResponse response; - public MemberServerResponseBuilder(final ExtractableResponse response) { + public MemberQueryResponseBuilder(final ExtractableResponse response) { this.response = response; } diff --git a/backend/baton/src/test/java/touch/baton/assure/member/support/query/RunnerQueryAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/member/support/query/RunnerQueryAssuredSupport.java new file mode 100644 index 000000000..ec9fecf84 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/member/support/query/RunnerQueryAssuredSupport.java @@ -0,0 +1,103 @@ +package touch.baton.assure.member.support.query; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.PathParams; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.service.dto.RunnerUpdateRequest; +import touch.baton.domain.member.query.controller.response.RunnerResponse; + +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerQueryAssuredSupport { + + private RunnerQueryAssuredSupport() { + } + + public static RunnerQueryBuilder 클라이언트_요청() { + return new RunnerQueryBuilder(); + } + + public static class RunnerQueryBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerQueryBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerQueryBuilder 러너_본인_프로필을_가지고_있는_액세스_토큰으로_조회한다() { + response = AssuredSupport.get("/api/v1/profile/runner/me", accessToken); + return this; + } + + public RunnerQueryBuilder 러너_프로필을_상세_조회한다(final Long 러너_식별자) { + response = AssuredSupport.get("/api/v1/profile/runner/{runnerId}", new PathParams(Map.of("runnerId", 러너_식별자))); + return this; + } + + public RunnerQueryResponseBuilder 서버_응답() { + return new RunnerQueryResponseBuilder(response); + } + } + + public static class RunnerQueryResponseBuilder { + + private final ExtractableResponse response; + + public RunnerQueryResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public static RunnerQueryBuilder 클라이언트_요청() { + return new RunnerQueryBuilder(); + } + + public static RunnerResponse.Detail 러너_프로필_상세_응답(final Runner 러너, final RunnerUpdateRequest 러너_본인_프로필_수정_요청) { + return new RunnerResponse.Detail( + 러너.getId(), + 러너_본인_프로필_수정_요청.name(), + 러너.getMember().getImageUrl().getValue(), + 러너.getMember().getGithubUrl().getValue(), + 러너_본인_프로필_수정_요청.introduction(), + 러너_본인_프로필_수정_요청.company(), + 러너_본인_프로필_수정_요청.technicalTags() + ); + } + + public void 러너_본인_프로필_조회_성공을_검증한다(final RunnerResponse.Mine 러너_본인_프로필_응답) { + final RunnerResponse.Mine actual = this.response.as(RunnerResponse.Mine.class); + + assertSoftly(softly -> { + softly.assertThat(actual.name()).isEqualTo(러너_본인_프로필_응답.name()); + softly.assertThat(actual.company()).isEqualTo(러너_본인_프로필_응답.company()); + softly.assertThat(actual.imageUrl()).isEqualTo(러너_본인_프로필_응답.imageUrl()); + softly.assertThat(actual.githubUrl()).isEqualTo(러너_본인_프로필_응답.githubUrl()); + softly.assertThat(actual.introduction()).isEqualTo(러너_본인_프로필_응답.introduction()); + softly.assertThat(actual.technicalTags()).isEqualTo(러너_본인_프로필_응답.technicalTags()); + }); + } + + public void 러너_프로필_상세_조회를_검증한다(final RunnerResponse.Detail 러너_프로필_상세_응답) { + final RunnerResponse.Detail actual = this.response.as(RunnerResponse.Detail.class); + + assertSoftly(softly -> { + softly.assertThat(actual.runnerId()).isNotNull(); + softly.assertThat(actual.name()).isEqualTo(러너_프로필_상세_응답.name()); + softly.assertThat(actual.imageUrl()).isEqualTo(러너_프로필_상세_응답.imageUrl()); + softly.assertThat(actual.githubUrl()).isEqualTo(러너_프로필_상세_응답.githubUrl()); + softly.assertThat(actual.introduction()).isEqualTo(러너_프로필_상세_응답.introduction()); + softly.assertThat(actual.company()).isEqualTo(러너_프로필_상세_응답.company()); + softly.assertThat(actual.technicalTags()).containsExactlyElementsOf(러너_프로필_상세_응답.technicalTags()); + } + ); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/member/support/query/SupporterQueryAssuredSupport.java similarity index 59% rename from backend/baton/src/test/java/touch/baton/assure/supporter/SupporterAssuredSupport.java rename to backend/baton/src/test/java/touch/baton/assure/member/support/query/SupporterQueryAssuredSupport.java index 1a27080bb..7fbe9149b 100644 --- a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/member/support/query/SupporterQueryAssuredSupport.java @@ -1,15 +1,11 @@ -package touch.baton.assure.supporter; +package touch.baton.assure.member.support.query; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; -import org.springframework.http.HttpStatus; import touch.baton.assure.common.AssuredSupport; import touch.baton.assure.common.PathParams; -import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.common.response.ErrorResponse; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.controller.response.SupporterResponse; -import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.query.controller.response.SupporterResponse; import java.util.List; import java.util.Map; @@ -17,21 +13,13 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; @SuppressWarnings("NonAsciiCharacters") -public class SupporterAssuredSupport { +public class SupporterQueryAssuredSupport { - private SupporterAssuredSupport() { + private SupporterQueryAssuredSupport() { } - public static SupporterClientRequestBuilder 클라이언트_요청() { - return new SupporterClientRequestBuilder(); - } - - public static SupporterUpdateRequest 서포터_본인_정보_수정_요청(final String 이름, - final String 회사, - final String 서포터_자기소개글, - final List 서포터_기술_태그_목록 - ) { - return new SupporterUpdateRequest(이름, 회사, 서포터_자기소개글, 서포터_기술_태그_목록); + public static SupporterQueryBuilder 클라이언트_요청() { + return new SupporterQueryBuilder(); } public static SupporterResponse.Profile 서포터_Profile_응답(final Supporter 서포터, final List 서포터_태그_목록) { @@ -57,42 +45,37 @@ private SupporterAssuredSupport() { ); } - public static class SupporterClientRequestBuilder { + public static class SupporterQueryBuilder { private ExtractableResponse response; private String accessToken; - public SupporterClientRequestBuilder 액세스_토큰으로_로그인_한다(final String 액세스_토큰) { + public SupporterQueryBuilder 액세스_토큰으로_로그인_한다(final String 액세스_토큰) { accessToken = 액세스_토큰; return this; } - public SupporterClientRequestBuilder 서포터_프로필을_서포터_식별자값으로_조회한다(final Long 서포터_식별자값) { + public SupporterQueryBuilder 서포터_프로필을_서포터_식별자값으로_조회한다(final Long 서포터_식별자값) { response = AssuredSupport.get("/api/v1/profile/supporter/{supporterId}", new PathParams(Map.of("supporterId", 서포터_식별자값))); return this; } - public SupporterClientRequestBuilder 서포터_본인_프로필을_수정한다(final SupporterUpdateRequest 서포터_업데이트_요청) { - response = AssuredSupport.patch("/api/v1/profile/supporter/me", accessToken, 서포터_업데이트_요청); - return this; - } - - public SupporterClientRequestBuilder 서포터_마이페이지를_액세스_토큰으로_조회한다() { + public SupporterQueryBuilder 서포터_마이페이지를_액세스_토큰으로_조회한다() { response = AssuredSupport.get("/api/v1/profile/supporter/me", accessToken); return this; } - public SupporterServerResponseBuilder 서버_응답() { - return new SupporterServerResponseBuilder(response); + public SupporterQueryResponseBuilder 서버_응답() { + return new SupporterQueryResponseBuilder(response); } } - public static class SupporterServerResponseBuilder { + public static class SupporterQueryResponseBuilder { private final ExtractableResponse response; - public SupporterServerResponseBuilder(final ExtractableResponse 응답) { + public SupporterQueryResponseBuilder(final ExtractableResponse 응답) { this.response = 응답; } @@ -111,22 +94,6 @@ public SupporterServerResponseBuilder(final ExtractableResponse 응답 ); } - public void 서포터_본인_프로필_수정_성공을_검증한다(final HttpStatus HTTP_STATUS) { - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value()); - softly.assertThat(response.header("Location")).isNotNull(); - }); - } - - public void 서포터_본인_프로필_수정_실패를_검증한다(final ClientErrorCode 클라이언트_에러_코드) { - final ErrorResponse actual = this.response.as(ErrorResponse.class); - - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(클라이언트_에러_코드.getHttpStatus().value()); - softly.assertThat(actual.errorCode()).isEqualTo(클라이언트_에러_코드.getErrorCode()); - }); - } - public void 서포터_마이페이지_프로필_조회_성공을_검증한다(final SupporterResponse.MyProfile 서포터_마이페이지_프로필_응답) { final SupporterResponse.MyProfile actual = this.response.as(SupporterResponse.MyProfile.class); diff --git a/backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationDeleteAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationDeleteAssuredTest.java new file mode 100644 index 000000000..76f47dc92 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationDeleteAssuredTest.java @@ -0,0 +1,73 @@ +package touch.baton.assure.notification.command; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.notification.support.command.NotificationDeleteSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.NotificationMessage; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.notification.command.vo.NotificationTitle; +import touch.baton.domain.notification.command.vo.NotificationType; +import touch.baton.domain.notification.command.vo.IsRead; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; + +import java.time.LocalDateTime; +import java.util.List; + +@SuppressWarnings("NonAsciiCharacters") +class NotificationDeleteAssuredTest extends AssuredTestConfig { + + @Test + void 로그인한_사용자가_자신의_알림을_하나_삭제한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest( + "테스트용 게시글 제목", + List.of("테스트용 태그1", "테스트용 태그2"), + "https://github.com/test", + LocalDateTime.now().plusDays(10), + "테스트용 구현 내용", + "테스트용 궁금한 내용", + "테스트용 남길 내용" + ); + + final Long 생성된_러너_게시글_식별자값 = RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너_게시글_등록_요청한다(게시글_생성_요청) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다() + .생성한_러너_게시글의_식별자값을_반환한다(); + + // when & then + final Notification 저장된_알림 = 러너_게시글_작성자에게_알림을_저장한다(생성된_러너_게시글_식별자값); + + NotificationDeleteSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .알림_삭제에_성공한다(저장된_알림.getId()) + + .서버_응답() + .알림_삭제_성공을_검증한다(); + } + + private Notification 러너_게시글_작성자에게_알림을_저장한다(final Long 생성된_러너_게시글_식별자값) { + final Member 헤나_사용자 = memberRepository.getAsRunnerByRunnerPostId(생성된_러너_게시글_식별자값); + + final Notification 저장되지_않은_알림 = Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(헤나_사용자) + .build(); + + return notificationCommandRepository.save(저장되지_않은_알림); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationUpdateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationUpdateAssuredTest.java new file mode 100644 index 000000000..14be54de0 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/notification/command/NotificationUpdateAssuredTest.java @@ -0,0 +1,73 @@ +package touch.baton.assure.notification.command; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.notification.support.command.NotificationUpdateSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.NotificationMessage; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.notification.command.vo.NotificationTitle; +import touch.baton.domain.notification.command.vo.NotificationType; +import touch.baton.domain.notification.command.vo.IsRead; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; + +import java.time.LocalDateTime; +import java.util.List; + +@SuppressWarnings("NonAsciiCharacters") +class NotificationUpdateAssuredTest extends AssuredTestConfig { + + @Test + void 로그인한_사용자의_알림_읽기_여부_기록을_업데이트한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest( + "테스트용 게시글 제목", + List.of("테스트용 태그1", "테스트용 태그2"), + "https://github.com/test", + LocalDateTime.now().plusDays(10), + "테스트용 구현 내용", + "테스트용 궁금한 내용", + "테스트용 남길 내용" + ); + + final Long 생성된_러너_게시글_식별자값 = RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너_게시글_등록_요청한다(게시글_생성_요청) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다() + .생성한_러너_게시글의_식별자값을_반환한다(); + + // when & then + final Notification 저장된_알림 = 러너_게시글_작성자에게_알림을_저장한다(생성된_러너_게시글_식별자값); + + NotificationUpdateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .알림_읽음_여부_기록_업데이트를_요청한다(저장된_알림.getId()) + + .서버_응답() + .알림_읽음_여부_기록_업데이트_성공을_검증한다(); + } + + private Notification 러너_게시글_작성자에게_알림을_저장한다(final Long 생성된_러너_게시글_식별자값) { + final Member 헤나_사용자 = memberRepository.getAsRunnerByRunnerPostId(생성된_러너_게시글_식별자값); + + final Notification 저장되지_않은_알림 = Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(헤나_사용자) + .build(); + + return notificationCommandRepository.save(저장되지_않은_알림); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/notification/query/NotificationQueryAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/notification/query/NotificationQueryAssuredTest.java new file mode 100644 index 000000000..e492d1a2d --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/notification/query/NotificationQueryAssuredTest.java @@ -0,0 +1,91 @@ +package touch.baton.assure.notification.query; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.notification.support.query.NotificationQuerySupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.NotificationMessage; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.notification.command.vo.NotificationTitle; +import touch.baton.domain.notification.command.vo.NotificationType; +import touch.baton.domain.notification.command.vo.IsRead; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +@SuppressWarnings("NonAsciiCharacters") +class NotificationQueryAssuredTest extends AssuredTestConfig { + + @Test + void 로그인한_사용자의_알림_목록을_조회한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest( + "테스트용 게시글 제목", + List.of("테스트용 태그1", "테스트용 태그2"), + "https://github.com/test", + LocalDateTime.now().plusDays(10), + "테스트용 구현 내용", + "테스트용 궁금한 내용", + "테스트용 남길 내용" + ); + + final Long 생성된_러너_게시글_식별자값 = RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너_게시글_등록_요청한다(게시글_생성_요청) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다() + .생성한_러너_게시글의_식별자값을_반환한다(); + + // when & then + final List 예상된_알림_목록 = 러너_게시글_작성자에게_5개의_알림을_저장한다(생성된_러너_게시글_식별자값); + + NotificationQuerySupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .로그인한_사용자의_알림_목록을_조회한다() + + .서버_응답() + .로그인한_사용자의_알림_목록_조회_성공을_검증한다(예상된_알림_목록); + } + + private List 러너_게시글_작성자에게_5개의_알림을_저장한다(final Long 생성된_러너_게시글_식별자값) { + final List 예상된_알림_목록 = new ArrayList<>(); + for (int 저장될_알림_카운트_수 = 1; 저장될_알림_카운트_수 <= 5; 저장될_알림_카운트_수++) { + final Notification 저장된_알림 = 러너_게시글_작성자에게_알림을_저장한다(생성된_러너_게시글_식별자값); + 예상된_알림_목록.add(저장된_알림); + } + Collections.sort(예상된_알림_목록, 알림_식별자값을_기준_내림차순으로_정렬한다()); + + return 예상된_알림_목록; + } + + private Notification 러너_게시글_작성자에게_알림을_저장한다(final Long 생성된_러너_게시글_식별자값) { + final Member 헤나_사용자 = memberRepository.getAsRunnerByRunnerPostId(생성된_러너_게시글_식별자값); + + final Notification 저장되지_않은_알림 = Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(헤나_사용자) + .build(); + + return notificationCommandRepository.save(저장되지_않은_알림); + } + + private Comparator 알림_식별자값을_기준_내림차순으로_정렬한다() { + return (left, right) -> left.getId() < right.getId() ? 1 : -1; + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationDeleteSupport.java b/backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationDeleteSupport.java new file mode 100644 index 000000000..2cc3b03f6 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationDeleteSupport.java @@ -0,0 +1,59 @@ +package touch.baton.assure.notification.support.command; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.PathParams; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +public class NotificationDeleteSupport { + + private NotificationDeleteSupport() { + } + + public static NotificationDeleteBuilder 클라이언트_요청() { + return new NotificationDeleteBuilder(); + } + + public static class NotificationDeleteBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public NotificationDeleteBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public NotificationDeleteBuilder 알림_삭제에_성공한다(final Long 알림_식별자값) { + response = AssuredSupport.delete("/api/v1/notifications/{notificationId}", + accessToken, + new PathParams(Map.of("notificationId", 알림_식별자값)) + ); + return this; + } + + public NotificationDeleteResponseBuilder 서버_응답() { + return new NotificationDeleteResponseBuilder(response); + } + } + + public static class NotificationDeleteResponseBuilder { + + private final ExtractableResponse response; + + public NotificationDeleteResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 알림_삭제_성공을_검증한다() { + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationUpdateSupport.java b/backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationUpdateSupport.java new file mode 100644 index 000000000..685668065 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/notification/support/command/NotificationUpdateSupport.java @@ -0,0 +1,59 @@ +package touch.baton.assure.notification.support.command; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.PathParams; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +public class NotificationUpdateSupport { + + private NotificationUpdateSupport() { + } + + public static NotificationUpdateBuilder 클라이언트_요청() { + return new NotificationUpdateBuilder(); + } + + public static class NotificationUpdateBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public NotificationUpdateBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public NotificationUpdateBuilder 알림_읽음_여부_기록_업데이트를_요청한다(final Long 알림_식별자값) { + response = AssuredSupport.patch("/api/v1/notifications/{notificationId}", + accessToken, + new PathParams(Map.of("notificationId", 알림_식별자값)) + ); + return this; + } + + public NotificationUpdateResponseBuilder 서버_응답() { + return new NotificationUpdateResponseBuilder(response); + } + } + + public static class NotificationUpdateResponseBuilder { + + private final ExtractableResponse response; + + public NotificationUpdateResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 알림_읽음_여부_기록_업데이트_성공을_검증한다() { + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/notification/support/query/NotificationQuerySupport.java b/backend/baton/src/test/java/touch/baton/assure/notification/support/query/NotificationQuerySupport.java new file mode 100644 index 000000000..bcb843af1 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/notification/support/query/NotificationQuerySupport.java @@ -0,0 +1,60 @@ +package touch.baton.assure.notification.support.query; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.query.controller.response.NotificationResponses; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +public class NotificationQuerySupport { + + private NotificationQuerySupport() { + } + + public static NotificationQueryBuilder 클라이언트_요청() { + return new NotificationQueryBuilder(); + } + + public static class NotificationQueryBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public NotificationQueryBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public NotificationQueryBuilder 로그인한_사용자의_알림_목록을_조회한다() { + response = AssuredSupport.get("/api/v1/notifications", accessToken); + return this; + } + + public NotificationQueryResponseBuilder 서버_응답() { + return new NotificationQueryResponseBuilder(response); + } + } + + public static class NotificationQueryResponseBuilder { + + private final ExtractableResponse response; + + public NotificationQueryResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 로그인한_사용자의_알림_목록_조회_성공을_검증한다(final List 알림_목록) { + final NotificationResponses.SimpleNotifications 조회된_알림_응답_목록 = this.response.as(new TypeRef<>() { + }); + + assertThat(조회된_알림_응답_목록).isEqualTo(NotificationResponses.SimpleNotifications.from(알림_목록)); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java index a416eb065..a04fc622f 100644 --- a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java @@ -8,13 +8,13 @@ import touch.baton.assure.common.PathParams; import touch.baton.assure.common.QueryParams; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.token.AccessToken; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; -import touch.baton.domain.oauth.token.Tokens; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.token.AccessToken; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.domain.oauth.command.token.Tokens; import java.time.LocalDateTime; import java.util.Map; @@ -91,6 +91,18 @@ public static class OauthClientRequestBuilder { return this; } + public OauthClientRequestBuilder 로그아웃을_요청한다(final AccessToken 액세스_토큰) { + response = AssuredSupport.patch("/api/v1/oauth/logout", 액세스_토큰.getValue()); + + return this; + } + + public OauthClientRequestBuilder 액세스_토큰_없이_로그아웃을_요청한다() { + response = AssuredSupport.patch("/api/v1/oauth/logout"); + + return this; + } + public OauthServerResponseBuilder 서버_응답() { return new OauthServerResponseBuilder(response); } @@ -153,5 +165,11 @@ public OauthServerResponseBuilder(final ExtractableResponse response) softly.assertThat(response.jsonPath().getString("errorCode")).isEqualTo(clientErrorCode.getErrorCode()); }); } + + public void 로그아웃이_성공한다() { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + }); + } } } diff --git a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthCreateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthCreateAssuredTest.java index 557c24292..b406c9c5e 100644 --- a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthCreateAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthCreateAssuredTest.java @@ -2,8 +2,8 @@ import org.junit.jupiter.api.Test; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.oauth.OauthType; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.oauth.command.OauthType; @SuppressWarnings("NonAsciiCharacters") class OauthCreateAssuredTest extends AssuredTestConfig { @@ -19,7 +19,7 @@ class OauthCreateAssuredTest extends AssuredTestConfig { OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.hyenaAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.hyenaAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다(); diff --git a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthDeleteAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthDeleteAssuredTest.java new file mode 100644 index 000000000..adb556eef --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthDeleteAssuredTest.java @@ -0,0 +1,67 @@ +package touch.baton.assure.oauth; + +import org.junit.jupiter.api.Test; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.common.exception.ClientErrorCode; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.token.Tokens; +import touch.baton.fixture.domain.MemberFixture; + +@SuppressWarnings("NonAsciiCharacters") +class OauthDeleteAssuredTest extends AssuredTestConfig { + + @Test + void 로그아웃을_성공한다() { + // given + OauthAssuredSupport + .클라이언트_요청() + .소셜_로그인을_위한_리다이렉트_URL을_요청한다(OauthType.GITHUB) + + .서버_응답() + .소셜_로그인을_위한_리다이렉트_URL_요청_성공을_검증한다(); + + final Tokens 액세스_토큰과_리프레시_토큰 = OauthAssuredSupport + .클라이언트_요청() + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) + + .서버_응답() + .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() + .액세스_토큰과_리프레시_토큰을_반환한다(MemberFixture.createEthan()); + + // when, then + OauthAssuredSupport + .클라이언트_요청() + .로그아웃을_요청한다(액세스_토큰과_리프레시_토큰.accessToken()) + + .서버_응답() + .로그아웃이_성공한다(); + } + + @Test + void 액세스_토큰이_없이_로그아웃을_요청하면_실패한다() { + // given + OauthAssuredSupport + .클라이언트_요청() + .소셜_로그인을_위한_리다이렉트_URL을_요청한다(OauthType.GITHUB) + + .서버_응답() + .소셜_로그인을_위한_리다이렉트_URL_요청_성공을_검증한다(); + + final Tokens 액세스_토큰과_리프레시_토큰 = OauthAssuredSupport + .클라이언트_요청() + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) + + .서버_응답() + .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() + .액세스_토큰과_리프레시_토큰을_반환한다(MemberFixture.createEthan()); + + // when, then + OauthAssuredSupport + .클라이언트_요청() + .액세스_토큰_없이_로그아웃을_요청한다() + + .서버_응답() + .오류가_발생한다(ClientErrorCode.OAUTH_AUTHORIZATION_VALUE_IS_NULL); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java index 3b0d97b2a..2a936f786 100644 --- a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java @@ -3,13 +3,13 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.Token; -import touch.baton.domain.oauth.token.Tokens; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.vo.ExpireDateFixture; import touch.baton.infra.auth.jwt.JwtEncoder; @@ -34,7 +34,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { final Tokens 액세스_토큰과_리프레시_토큰 = OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.ethanAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() @@ -59,7 +59,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { final Tokens 액세스_토큰과_리프레시_토큰 = OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.ethanAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() @@ -85,7 +85,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { final Tokens 액세스_토큰과_리프레시_토큰 = OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.ethanAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() @@ -113,7 +113,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { final Tokens 액세스_토큰과_리프레시_토큰 = OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.ethanAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() @@ -141,7 +141,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { final Tokens 헤나_액세스_토큰과_리프레시_토큰 = OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.hyenaAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.hyenaAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() @@ -149,7 +149,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { final Tokens 에단_액세스_토큰과_리프레시_토큰 = OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.ethanAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() @@ -181,7 +181,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { final Tokens 액세스_토큰과_리프레시_토큰 = OauthAssuredSupport .클라이언트_요청() - .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, MockAuthCodes.ethanAuthCode()) + .AuthCode를_통해_소셜_토큰을_발급_받은_후_사용자를_회원가입_한다(OauthType.GITHUB, FakeAuthCodes.ethanAuthCode()) .서버_응답() .AuthCode를_통해_소셜_토큰_발급_및_사용자_회원가입에_성공한다() diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestMemberQueryRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestMemberQueryRepository.java new file mode 100644 index 000000000..e7e0b8f70 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestMemberQueryRepository.java @@ -0,0 +1,33 @@ +package touch.baton.assure.repository; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.repository.MemberCommandRepository; +import touch.baton.domain.member.command.vo.SocialId; + +import java.util.Optional; + +public interface TestMemberQueryRepository extends MemberCommandRepository { + + default Member getBySocialId(final SocialId socialId) { + return findBySocialId(socialId) + .orElseThrow(() -> new IllegalArgumentException("테스트에서 Member 를 SocialId 로 조회할 수 없습니다.")); + }; + + Optional findBySocialId(final SocialId socialId); + + default Member getAsRunnerByRunnerPostId(final Long runnerPostId) { + return findAsRunnerByRunnerPostId(runnerPostId) + .orElseThrow(() -> new IllegalArgumentException("테스트에서 Member 를 runnerPostId로 러너 게시글의 작성자(Runner)로서 조회할 수 없습니다.")); + }; + + @Query(""" + select rp.runner.member + from RunnerPost rp + join fetch Runner r on r.id = rp.runner.id + join fetch Member m on m.id = r.member.id + where rp.id = :runnerPostId + """) + Optional findAsRunnerByRunnerPostId(@Param("runnerPostId") final Long runnerPostId); +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestMemberRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestMemberRepository.java deleted file mode 100644 index 8234d1963..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestMemberRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package touch.baton.assure.repository; - -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.member.vo.SocialId; - -import java.util.Optional; - -public interface TestMemberRepository extends MemberRepository { - - default Member getBySocialId(final SocialId socialId) { - return findBySocialId(socialId) - .orElseThrow(() -> new IllegalArgumentException("테스트에서 Runner 를 SocialId 로 조회할 수 없습니다.")); - }; - - Optional findBySocialId(final SocialId socialId); -} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestNotificationCommandRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestNotificationCommandRepository.java new file mode 100644 index 000000000..431040f04 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestNotificationCommandRepository.java @@ -0,0 +1,6 @@ +package touch.baton.assure.repository; + +import touch.baton.domain.notification.command.repository.NotificationCommandRepository; + +public interface TestNotificationCommandRepository extends NotificationCommandRepository { +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java index 7f37c856c..d19ddd643 100644 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java @@ -5,9 +5,9 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; public interface TestRefreshTokenRepository extends JpaRepository { diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostQueryRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostQueryRepository.java new file mode 100644 index 000000000..8c96a10ca --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostQueryRepository.java @@ -0,0 +1,27 @@ +package touch.baton.assure.repository; + +import org.springframework.context.annotation.Profile; +import org.springframework.data.repository.query.Param; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.repository.dto.RunnerPostApplicantCountDto; +import touch.baton.domain.runnerpost.query.repository.RunnerPostQueryRepository; + +import java.util.List; + +@Profile("test") +public interface TestRunnerPostQueryRepository extends RunnerPostQueryRepository { + + default RunnerPost getByRunnerPostId(@Param("runnerPostId") final Long runnerPostId) { + return joinMemberByRunnerPostId(runnerPostId) + .orElseThrow(() -> new IllegalArgumentException("테스트에서 RunnerPost 를 러너 게시글 식별자값(id) 로 조회할 수 없습니다.")); + } + + default long countApplicantByRunnerPostId(final Long runnerPostId) { + final List foundApplicants = countApplicantsByRunnerPostIds(List.of(runnerPostId)); + if (foundApplicants.isEmpty()) { + throw new IllegalArgumentException("테스트에서 러너 게시글 식별자값으로 서포터 지원자 수 조회에 실패하였습니다."); + } + + return foundApplicants.get(0).applicantCount(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostReadRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostReadRepository.java deleted file mode 100644 index 3e1d5244d..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostReadRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package touch.baton.assure.repository; - -import touch.baton.domain.runnerpost.repository.RunnerPostReadRepository; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountDto; - -import java.util.List; - -public interface TestRunnerPostReadRepository extends RunnerPostReadRepository { - - default Long countApplicantByRunnerPostId(final Long runnerPostId) { - final List foundApplicants = countApplicantsByRunnerPostIds(List.of(runnerPostId)); - if (foundApplicants.isEmpty()) { - throw new IllegalArgumentException("테스트에서 러너 게시글 식별자값으로 서포터 지원자 수 조회에 실패하였습니다."); - } - - return foundApplicants.get(0).applicantCount(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostRepository.java deleted file mode 100644 index 2be576501..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerPostRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package touch.baton.assure.repository; - -import org.springframework.data.repository.query.Param; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.RunnerPostRepository; - -public interface TestRunnerPostRepository extends RunnerPostRepository { - - default RunnerPost getByRunnerPostId(@Param("runnerPostId") final Long runnerPostId) { - return joinMemberByRunnerPostId(runnerPostId) - .orElseThrow(() -> new IllegalArgumentException("테스트에서 RunnerPost 를 러너 게시글 식별자값(id) 로 조회할 수 없습니다.")); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerQueryRepository.java similarity index 73% rename from backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerRepository.java rename to backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerQueryRepository.java index 2090c9465..e7719f7f5 100644 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerRepository.java +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestRunnerQueryRepository.java @@ -2,13 +2,13 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.repository.RunnerRepository; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.member.query.repository.RunnerQueryRepository; import java.util.Optional; -public interface TestRunnerRepository extends RunnerRepository { +public interface TestRunnerQueryRepository extends RunnerQueryRepository { default Runner getBySocialId(final SocialId socialId) { return joinMemberBySocialId(socialId) diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterQueryRepository.java similarity index 72% rename from backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRepository.java rename to backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterQueryRepository.java index de944ecd9..bb152099f 100644 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRepository.java +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterQueryRepository.java @@ -2,13 +2,13 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.repository.SupporterRepository; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.member.query.repository.SupporterQueryRepository; import java.util.Optional; -public interface TestSupporterRepository extends SupporterRepository { +public interface TestSupporterQueryRepository extends SupporterQueryRepository { default Supporter getBySocialId(final SocialId socialId) { return joinMemberBySocialId(socialId) diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostQueryRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostQueryRepository.java new file mode 100644 index 000000000..a77e69712 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostQueryRepository.java @@ -0,0 +1,12 @@ +package touch.baton.assure.repository; + +import org.springframework.context.annotation.Profile; +import touch.baton.domain.member.query.repository.SupporterRunnerPostQueryRepository; + +@Profile("test") +public interface TestSupporterRunnerPostQueryRepository extends SupporterRunnerPostQueryRepository { + + default Long getApplicantCountByRunnerPostId(final Long runnerPostId) { + return countByRunnerPostId(runnerPostId).orElse(0L); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostRepository.java deleted file mode 100644 index 052b798c9..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterRunnerPostRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package touch.baton.assure.repository; - -import touch.baton.domain.supporter.repository.SupporterRunnerPostRepository; - -public interface TestSupporterRunnerPostRepository extends SupporterRunnerPostRepository { - - default Long getApplicantCountByRunnerPostId(final Long runnerPostId) { - return countByRunnerPostId(runnerPostId).orElse(0L); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestTagQuerydslRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestTagQuerydslRepository.java new file mode 100644 index 000000000..d1f557b8f --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestTagQuerydslRepository.java @@ -0,0 +1,13 @@ +package touch.baton.assure.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.stereotype.Repository; +import touch.baton.domain.tag.query.repository.TagQuerydslRepository; + +@Repository +public class TestTagQuerydslRepository extends TagQuerydslRepository { + + public TestTagQuerydslRepository(final JPAQueryFactory queryFactory) { + super(queryFactory); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestTagRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestTagRepository.java deleted file mode 100644 index b7e614433..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestTagRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package touch.baton.assure.repository; - -import touch.baton.domain.tag.repository.TagRepository; - -public interface TestTagRepository extends TagRepository { -} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagQueryRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagQueryRepository.java new file mode 100644 index 000000000..b5c6d2172 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagQueryRepository.java @@ -0,0 +1,6 @@ +package touch.baton.assure.repository; + +import touch.baton.domain.technicaltag.query.repository.TechnicalTagQueryRepository; + +public interface TestTechnicalTagQueryRepository extends TechnicalTagQueryRepository { +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagRepository.java deleted file mode 100644 index 252242b71..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestTechnicalTagRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package touch.baton.assure.repository; - -import touch.baton.domain.technicaltag.repository.TechnicalTagRepository; - -public interface TestTechnicalTagRepository extends TechnicalTagRepository { -} diff --git a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/runner/RunnerAssuredSupport.java deleted file mode 100644 index 154b91427..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerAssuredSupport.java +++ /dev/null @@ -1,165 +0,0 @@ -package touch.baton.assure.runner; - -import io.restassured.response.ExtractableResponse; -import io.restassured.response.Response; -import org.springframework.http.HttpHeaders; -import touch.baton.assure.common.AssuredSupport; -import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.assure.common.PathParams; -import touch.baton.domain.common.exception.ClientErrorCode; -import touch.baton.domain.common.response.ErrorResponse; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.controller.response.RunnerProfileResponse; -import touch.baton.domain.runner.controller.response.RunnerResponse; -import touch.baton.domain.runner.service.dto.RunnerUpdateRequest; - -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.SoftAssertions.assertSoftly; - -@SuppressWarnings("NonAsciiCharacters") -public class RunnerAssuredSupport { - - private RunnerAssuredSupport() { - } - - public static RunnerClientRequestBuilder 클라이언트_요청() { - return new RunnerClientRequestBuilder(); - } - - public static RunnerResponse.MyProfile 러너_본인_프로필_응답(final Runner 러너, final List 러너_태그_목록) { - return new RunnerResponse.MyProfile( - 러너.getMember().getMemberName().getValue(), - 러너.getMember().getCompany().getValue(), - 러너.getMember().getImageUrl().getValue(), - 러너.getMember().getGithubUrl().getValue(), - 러너.getIntroduction().getValue(), - 러너_태그_목록 - ); - } - - public static RunnerUpdateRequest 러너_본인_프로필_수정_요청(final String 러너_이름, - final String 회사, - final String 러너_소개글, - final List 러너_기술태그_목록 - ) { - return new RunnerUpdateRequest(러너_이름, 회사, 러너_소개글, 러너_기술태그_목록); - } - - public static RunnerProfileResponse.Detail 러너_프로필_상세_응답(final Runner 러너) { - final List 태그_목록 = 러너.getRunnerTechnicalTags().getRunnerTechnicalTags().stream() - .map(runnerTechnicalTag -> runnerTechnicalTag.getTechnicalTag().getTagName().getValue()) - .toList(); - - return new RunnerProfileResponse.Detail( - 러너.getId(), - 러너.getMember().getMemberName().getValue(), - 러너.getMember().getImageUrl().getValue(), - 러너.getMember().getGithubUrl().getValue(), - 러너.getIntroduction().getValue(), - 러너.getMember().getCompany().getValue(), - 태그_목록 - ); - } - - public static RunnerProfileResponse.Detail 러너_프로필_상세_응답(final Runner 러너, final RunnerUpdateRequest 러너_본인_프로필_수정_요청) { - return new RunnerProfileResponse.Detail( - 러너.getId(), - 러너_본인_프로필_수정_요청.name(), - 러너.getMember().getImageUrl().getValue(), - 러너.getMember().getGithubUrl().getValue(), - 러너_본인_프로필_수정_요청.introduction(), - 러너_본인_프로필_수정_요청.company(), - 러너_본인_프로필_수정_요청.technicalTags() - ); - } - - public static class RunnerClientRequestBuilder { - - private ExtractableResponse response; - - private String accessToken; - - public RunnerClientRequestBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { - this.accessToken = 액세스_토큰; - return this; - } - - public RunnerClientRequestBuilder 러너_본인_프로필을_가지고_있는_액세스_토큰으로_조회한다() { - response = AssuredSupport.get("/api/v1/profile/runner/me", accessToken); - return this; - } - - public RunnerClientRequestBuilder 러너_프로필을_상세_조회한다(final Long 러너_식별자) { - response = AssuredSupport.get("/api/v1/profile/runner/{runnerId}", new PathParams(Map.of("runnerId", 러너_식별자))); - return this; - } - - public RunnerClientRequestBuilder 러너_본인_프로필을_수정한다(final RunnerUpdateRequest 러너_업데이트_요청) { - response = AssuredSupport.patch("/api/v1/profile/runner/me", accessToken, 러너_업데이트_요청); - return this; - } - - public RunnerServerResponseBuilder 서버_응답() { - return new RunnerServerResponseBuilder(response); - } - } - - public static class RunnerServerResponseBuilder { - - private final ExtractableResponse response; - - public RunnerServerResponseBuilder(final ExtractableResponse response) { - this.response = response; - } - - public static RunnerClientRequestBuilder 클라이언트_요청() { - return new RunnerClientRequestBuilder(); - } - - public void 러너_본인_프로필_조회_성공을_검증한다(final RunnerResponse.MyProfile 러너_본인_프로필_응답) { - final RunnerResponse.MyProfile actual = this.response.as(RunnerResponse.MyProfile.class); - - assertSoftly(softly -> { - softly.assertThat(actual.name()).isEqualTo(러너_본인_프로필_응답.name()); - softly.assertThat(actual.company()).isEqualTo(러너_본인_프로필_응답.company()); - softly.assertThat(actual.imageUrl()).isEqualTo(러너_본인_프로필_응답.imageUrl()); - softly.assertThat(actual.githubUrl()).isEqualTo(러너_본인_프로필_응답.githubUrl()); - softly.assertThat(actual.introduction()).isEqualTo(러너_본인_프로필_응답.introduction()); - softly.assertThat(actual.technicalTags()).isEqualTo(러너_본인_프로필_응답.technicalTags()); - }); - } - - public void 러너_프로필_상세_조회를_검증한다(final RunnerProfileResponse.Detail 러너_프로필_상세_응답) { - final RunnerProfileResponse.Detail actual = this.response.as(RunnerProfileResponse.Detail.class); - - assertSoftly(softly -> { - softly.assertThat(actual.runnerId()).isNotNull(); - softly.assertThat(actual.name()).isEqualTo(러너_프로필_상세_응답.name()); - softly.assertThat(actual.imageUrl()).isEqualTo(러너_프로필_상세_응답.imageUrl()); - softly.assertThat(actual.githubUrl()).isEqualTo(러너_프로필_상세_응답.githubUrl()); - softly.assertThat(actual.introduction()).isEqualTo(러너_프로필_상세_응답.introduction()); - softly.assertThat(actual.company()).isEqualTo(러너_프로필_상세_응답.company()); - softly.assertThat(actual.technicalTags()).containsExactlyElementsOf(러너_프로필_상세_응답.technicalTags()); - } - ); - } - - public void 러너_본인_프로필_수정_성공을_검증한다(final HttpStatusAndLocationHeader 응답상태_및_로케이션) { - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(응답상태_및_로케이션.getHttpStatus().value()); - softly.assertThat(response.header(HttpHeaders.LOCATION)).contains(응답상태_및_로케이션.getLocation()); - }); - } - - public void 러너_본인_프로필_수정_실패를_검증한다(final ClientErrorCode 클라이언트_에러_코드) { - final ErrorResponse actual = this.response.as(ErrorResponse.class); - - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(클라이언트_에러_코드.getHttpStatus().value()); - softly.assertThat(actual.errorCode()).isEqualTo(클라이언트_에러_코드.getErrorCode()); - }); - } - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerReadByRunnerIdAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runner/RunnerReadByRunnerIdAssuredTest.java deleted file mode 100644 index f5310a32a..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/runner/RunnerReadByRunnerIdAssuredTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package touch.baton.assure.runner; - -import org.junit.jupiter.api.Test; -import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.service.dto.RunnerUpdateRequest; - -import java.util.List; - -import static org.springframework.http.HttpStatus.NO_CONTENT; -import static touch.baton.assure.runner.RunnerAssuredSupport.러너_본인_프로필_수정_요청; -import static touch.baton.assure.runner.RunnerAssuredSupport.러너_프로필_상세_응답; - -@SuppressWarnings("NonAsciiCharacters") -class RunnerReadByRunnerIdAssuredTest extends AssuredTestConfig { - - @Test - void 러너_프로필_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - - final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); - final Runner 러너_헤나 = runnerRepository.getBySocialId(헤나_소셜_아이디); - - final RunnerUpdateRequest 러너_본인_프로필_수정_요청 = 러너_본인_프로필_수정_요청("수정된_헤나", "수정된_회사", "수정된_러너_소개글", List.of("자바", "스프링")); - RunnerAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너_본인_프로필을_수정한다(러너_본인_프로필_수정_요청) - - .서버_응답() - .러너_본인_프로필_수정_성공을_검증한다(new HttpStatusAndLocationHeader(NO_CONTENT, "/api/v1/profile/runner/me")); - - // when, then - RunnerAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너_프로필을_상세_조회한다(러너_헤나.getId()) - - .서버_응답() - .러너_프로필_상세_조회를_검증한다(러너_프로필_상세_응답(러너_헤나, 러너_본인_프로필_수정_요청)); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java deleted file mode 100644 index 1f3d0b374..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredSupport.java +++ /dev/null @@ -1,436 +0,0 @@ -package touch.baton.assure.runnerpost; - -import io.restassured.common.mapper.TypeRef; -import io.restassured.response.ExtractableResponse; -import io.restassured.response.Response; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import touch.baton.assure.common.AssuredSupport; -import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.assure.common.PathParams; -import touch.baton.assure.common.QueryParams; -import touch.baton.domain.common.response.ErrorResponse; -import touch.baton.domain.common.response.PageResponse; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.controller.response.RunnerResponse; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.controller.response.SupporterRunnerPostResponse; -import touch.baton.domain.runnerpost.controller.response.SupporterRunnerPostResponses; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; -import touch.baton.domain.runnerpost.service.dto.RunnerPostUpdateRequest; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static org.springframework.http.HttpHeaders.LOCATION; - -@SuppressWarnings("NonAsciiCharacters") -public class RunnerPostAssuredSupport { - - private RunnerPostAssuredSupport() { - } - - public static RunnerPostClientRequestBuilder 클라이언트_요청() { - return new RunnerPostClientRequestBuilder(); - } - - public static RunnerPostUpdateRequest.SelectSupporter 러너의_서포터_선택_요청(final Long 러너가_선택한_서포터_식별자값) { - return new RunnerPostUpdateRequest.SelectSupporter(러너가_선택한_서포터_식별자값); - } - - public static RunnerPostResponse.Detail 러너_게시글_Detail_응답(final Long 러너_게시글_식별자값, - final String 제목, - final String 구현_내용, - final String 궁금한_내용, - final String 참고_사항, - final String 풀_리퀘스트, - final LocalDateTime 마감기한, - final int 조회수, - final long 서포터_지원자수, - final ReviewStatus 리뷰_상태, - final boolean 주인_여부, - final boolean 서포터_지원_여부, - final Runner 러너, - final List 태그_목록 - ) { - return new RunnerPostResponse.Detail( - 러너_게시글_식별자값, - 제목, - 구현_내용, - 궁금한_내용, - 참고_사항, - 풀_리퀘스트, - 마감기한, - 조회수, - 서포터_지원자수, - 리뷰_상태, - 주인_여부, - 서포터_지원_여부, - 태그_목록, - RunnerResponse.Detail.from(러너) - ); - } - - public static PageResponse 서포터와_연관된_러너_게시글_페이징_응답(final Pageable 페이징_정보, - final List 서포터가_연관된_러너_게시글_목록 - ) { - final Page 페이징된_서포터가_연관된_러너_게시글 = new PageImpl<>(서포터가_연관된_러너_게시글_목록, 페이징_정보, 서포터가_연관된_러너_게시글_목록.size()); - final PageResponse 페이징된_서포터가_연관된_러너_게시글_응답 = PageResponse.from(페이징된_서포터가_연관된_러너_게시글); - - return 페이징된_서포터가_연관된_러너_게시글_응답; - } - - public static RunnerPostResponse.ReferencedBySupporter 서포터와_연관된_러너_게시글_응답(final RunnerPost 러너_게시글, - final List 러너_게시글_태그_목록, - final int 조회수, - final long 러너_게시글에_지원한_서포터수, - final ReviewStatus 리뷰_상태 - ) { - return new RunnerPostResponse.ReferencedBySupporter( - 러너_게시글.getId(), - 러너_게시글.getTitle().getValue(), - 러너_게시글.getDeadline().getValue(), - 러너_게시글_태그_목록, - 조회수, - 러너_게시글에_지원한_서포터수, - 리뷰_상태.name() - ); - } - - public static RunnerPostResponse.Simple 러너_게시글_Simple_응답(final RunnerPost 러너_게시글, - final int 조회수, - final long 지원한_서포터_수, - final ReviewStatus 리뷰_상태, - final List 러너_게시글_태그_목록 - ) { - return new RunnerPostResponse.Simple( - 러너_게시글.getId(), - 러너_게시글.getTitle().getValue(), - 러너_게시글.getDeadline().getValue(), - 조회수, - 지원한_서포터_수, - 리뷰_상태.name(), - RunnerResponse.Simple.from(러너_게시글.getRunner()), - 러너_게시글_태그_목록 - ); - } - - public static PageResponse 러너_게시글_전체_Simple_페이징_응답(final Pageable 페이징_정보, - final List 러너_게시글_목록 - ) { - final Page 페이징된_러너_게시글 = new PageImpl<>(러너_게시글_목록, 페이징_정보, 러너_게시글_목록.size()); - final PageResponse 페이징된_러너_게시글_응답 = PageResponse.from(페이징된_러너_게시글); - - return 페이징된_러너_게시글_응답; - } - - public static RunnerPostResponse.SimpleInMyPage 마이페이지_러너_게시글_SimpleInMyPage_응답(final RunnerPost 러너_게시글, - final Long 지원한_서포터_식별자값, - final int 조회수, - final long 지원한_서포터_수, - final ReviewStatus 리뷰_상태, - final List 러너_게시글_태그_목록 - ) { - return new RunnerPostResponse.SimpleInMyPage( - 러너_게시글.getId(), - 지원한_서포터_식별자값, - 러너_게시글.getTitle().getValue(), - 러너_게시글.getDeadline().getValue(), - 러너_게시글_태그_목록, - 조회수, - 지원한_서포터_수, - 리뷰_상태.name(), - 러너_게시글.getIsReviewed().getValue() - ); - } - - public static PageResponse 마이페이지_러너_게시글_응답(final Pageable 페이징_정보, - final List 마이페이지_러너_게시글_목록 - ) { - final Page 페이징된_마이페이지_러너_게시글 = new PageImpl<>(마이페이지_러너_게시글_목록, 페이징_정보, 마이페이지_러너_게시글_목록.size()); - final PageResponse 페이징된_마이페이지_러너_게시글_응답 = PageResponse.from(페이징된_마이페이지_러너_게시글); - - return 페이징된_마이페이지_러너_게시글_응답; - } - - public static SupporterRunnerPostResponse.Detail 지원한_서포터_응답(final Supporter 지원한_서포터, - final int 서포터의_리뷰수, - final String 지원한_서포터_어필_메시지, - final List 서포터_기술_태그_목록 - ) { - return new SupporterRunnerPostResponse.Detail( - 지원한_서포터.getId(), - 지원한_서포터.getMember().getMemberName().getValue(), - 지원한_서포터.getMember().getCompany().getValue(), - 서포터의_리뷰수, - 지원한_서포터.getMember().getImageUrl().getValue(), - 지원한_서포터_어필_메시지, - 서포터_기술_태그_목록 - ); - } - - public static SupporterRunnerPostResponses.Detail 지원한_서포터_응답_목록_응답(final List 지원한_서포터_응답_목록) { - return SupporterRunnerPostResponses.Detail.from(지원한_서포터_응답_목록); - } - - public static class RunnerPostClientRequestBuilder { - - private ExtractableResponse response; - - private String accessToken; - - public RunnerPostClientRequestBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { - this.accessToken = 액세스_토큰; - return this; - } - - public RunnerPostClientRequestBuilder 러너_게시글_등록_요청한다(final RunnerPostCreateRequest 게시글_생성_요청) { - response = AssuredSupport.post("/api/v1/posts/runner", accessToken, 게시글_생성_요청); - return this; - } - - public RunnerPostClientRequestBuilder 러너_게시글_식별자값으로_러너_게시글을_조회한다(final Long 러너_게시글_식별자값) { - response = AssuredSupport.get( - "/api/v1/posts/runner/{runnerPostId}", - accessToken, - new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값))); - return this; - } - - public RunnerPostClientRequestBuilder 러너_게시글_식별자값으로_지원한_서포터_목록을_조회한다(final Long 러너_게시글_식별자값) { - response = AssuredSupport.get( - "/api/v1/posts/runner/{runnerPostId}/supporters", - accessToken, - new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값))); - return this; - } - - public RunnerPostClientRequestBuilder 서포터와_연관된_러너_게시글_페이징을_조회한다(final Long 서포터_식별자값, - final ReviewStatus 리뷰_진행_상태, - final Pageable 페이징_정보 - ) { - final Map queryParams = Map.of( - "supporterId", 서포터_식별자값, - "reviewStatus", 리뷰_진행_상태, - "size", 페이징_정보.getPageSize(), - "page", 페이징_정보.getPageNumber() - ); - - response = AssuredSupport.get("/api/v1/posts/runner/search", new QueryParams(queryParams)); - return this; - } - - public RunnerPostClientRequestBuilder 마이페이지_러너_게시글_페이징을_조회한다(final ReviewStatus 리뷰_상태, final Pageable 페이징_정보) { - final Map queryParams = Map.of( - "reviewStatus", 리뷰_상태, - "size", 페이징_정보.getPageSize(), - "page", 페이징_정보.getPageNumber() - ); - - response = AssuredSupport.get("/api/v1/posts/runner/me/runner", accessToken, new QueryParams(queryParams)); - return this; - } - - public RunnerPostClientRequestBuilder 리뷰_상태로_전체_러너_게시글_페이징을_조회한다(final Pageable 페이징_정보, final ReviewStatus 리뷰_상태) { - final Map queryParams = Map.of( - "size", 페이징_정보.getPageSize(), - "page", 페이징_정보.getPageNumber(), - "reviewStatus", 리뷰_상태 - ); - - response = AssuredSupport.get("/api/v1/posts/runner", new QueryParams(queryParams)); - return this; - } - - public RunnerPostClientRequestBuilder 태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징을_조회한다(final Pageable 페이징_정보, final String 태그_이름, final ReviewStatus 리뷰_상태) { - final Map queryParams = Map.of( - "size", 페이징_정보.getPageSize(), - "page", 페이징_정보.getPageNumber(), - "tagName", 태그_이름, - "reviewStatus", 리뷰_상태 - ); - - response = AssuredSupport.get("/api/v1/posts/runner/tags/search", new QueryParams(queryParams)); - return this; - } - - public RunnerPostClientRequestBuilder 서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(final Long 게시글_식별자) { - response = AssuredSupport.patch("/api/v1/posts/runner/{runnerPostId}/done", accessToken, new PathParams(Map.of("runnerPostId", 게시글_식별자))); - return this; - } - - public RunnerPostClientRequestBuilder 러너_게시글_식별자값으로_러너_게시글을_삭제한다(final Long 러너_게시글_식별자값) { - response = AssuredSupport.delete("/api/v1/posts/runner/{runnerPostId}", accessToken, new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값))); - return this; - } - - public RunnerPostClientRequestBuilder 러너가_서포터를_선택한다(final Long 게시글_식별자값, - final RunnerPostUpdateRequest.SelectSupporter 서포터_선택_요청_정보 - ) { - response = AssuredSupport.patch("/api/v1/posts/runner/{runnerPostId}/supporters", - accessToken, - new PathParams(Map.of("runnerPostId", 게시글_식별자값)), - 서포터_선택_요청_정보 - ); - return this; - } - - public RunnerPostClientRequestBuilder 로그인한_서포터의_러너_게시글_페이징을_조회한다(final ReviewStatus 리뷰_진행_상태, - final Pageable 페이징_정보 - ) { - final Map queryParams = Map.of( - "reviewStatus", 리뷰_진행_상태, - "size", 페이징_정보.getPageSize(), - "page", 페이징_정보.getPageNumber() - ); - - response = AssuredSupport.get("/api/v1/posts/runner/me/supporter", accessToken, new QueryParams(queryParams)); - return this; - } - - public RunnerPostServerResponseBuilder 서버_응답() { - return new RunnerPostServerResponseBuilder(response); - } - } - - public static class RunnerPostServerResponseBuilder { - - private final ExtractableResponse response; - - public RunnerPostServerResponseBuilder(final ExtractableResponse response) { - this.response = response; - } - - public void 러너_게시글_단건_조회_성공을_검증한다(final RunnerPostResponse.Detail 러너_게시글_응답) { - final RunnerPostResponse.Detail actual = this.response.as(RunnerPostResponse.Detail.class); - - assertSoftly(softly -> { - softly.assertThat(actual.runnerPostId()).isEqualTo(러너_게시글_응답.runnerPostId()); - softly.assertThat(actual.title()).isEqualTo(러너_게시글_응답.title()); - softly.assertThat(actual.implementedContents()).isEqualTo(러너_게시글_응답.implementedContents()); - softly.assertThat(actual.curiousContents()).isEqualTo(러너_게시글_응답.curiousContents()); - softly.assertThat(actual.postscriptContents()).isEqualTo(러너_게시글_응답.postscriptContents()); - softly.assertThat(actual.deadline()).isEqualToIgnoringSeconds(러너_게시글_응답.deadline()); - softly.assertThat(actual.watchedCount()).isEqualTo(러너_게시글_응답.watchedCount()); - softly.assertThat(actual.applicantCount()).isEqualTo(러너_게시글_응답.applicantCount()); - softly.assertThat(actual.reviewStatus()).isEqualTo(러너_게시글_응답.reviewStatus()); - softly.assertThat(actual.tags()).isEqualTo(러너_게시글_응답.tags()); - softly.assertThat(actual.deadline()).isEqualToIgnoringSeconds(러너_게시글_응답.deadline()); - softly.assertThat(actual.runnerProfile().name()).isEqualTo(러너_게시글_응답.runnerProfile().name()); - softly.assertThat(actual.runnerProfile().company()).isEqualTo(러너_게시글_응답.runnerProfile().company()); - softly.assertThat(actual.runnerProfile().imageUrl()).isEqualTo(러너_게시글_응답.runnerProfile().imageUrl()); - softly.assertThat(actual.runnerProfile().runnerId()).isEqualTo(러너_게시글_응답.runnerProfile().runnerId()); - softly.assertThat(actual.watchedCount()).isEqualTo(러너_게시글_응답.watchedCount()); - softly.assertThat(actual.runnerPostId()).isEqualTo(러너_게시글_응답.runnerPostId()); - } - ); - } - - public void 마이페이지_러너_게시글_페이징_조회_성공을_검증한다(final PageResponse 마이페이지_러너_게시글_페이징_응답) { - final PageResponse actual = this.response.as(new TypeRef<>() { - - }); - - assertSoftly(softly -> { - softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); - softly.assertThat(actual.data()).isEqualTo(마이페이지_러너_게시글_페이징_응답.data()); - } - ); - } - - public void 서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다(final PageResponse 서포터와_연관된_러너_게시글_페이징_응답) { - final PageResponse actual = this.response.as(new TypeRef<>() { - - }); - - assertSoftly(softly -> { - softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); - softly.assertThat(actual.data()).isEqualTo(서포터와_연관된_러너_게시글_페이징_응답.data()); - } - ); - } - - public void 전체_러너_게시글_페이징_조회_성공을_검증한다(final PageResponse 전체_러너_게시글_페이징_응답) { - final PageResponse actual = this.response.as(new TypeRef<>() { - - }); - - assertSoftly(softly -> { - softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); - softly.assertThat(actual.data()).isEqualTo(전체_러너_게시글_페이징_응답.data()); - } - ); - } - - public void 지원한_서포터_목록_조회_성공을_검증한다(final SupporterRunnerPostResponses.Detail 전체_러너_게시글_페이징_응답) { - final SupporterRunnerPostResponses.Detail actual = this.response.as(new TypeRef<>() { - - }); - - assertSoftly(softly -> { - softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); - softly.assertThat(actual.data()).isEqualTo(전체_러너_게시글_페이징_응답.data()); - } - ); - } - - public void 태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다(final PageResponse 러너_게시글_페이징_응답) { - final PageResponse actual = this.response.as(new TypeRef<>() { - }); - - assertSoftly(softly -> { - softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); - softly.assertThat(actual.data()).isEqualTo(러너_게시글_페이징_응답.data()); - } - ); - } - - public void 러너_게시글_삭제_성공을_검증한다(final HttpStatus HTTP_STATUS) { - assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value()); - } - - public void 러너_게시글_삭제_실패를_검증한다(final HttpStatus HTTP_STATUS) { - assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value()); - } - - public void 러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) { - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(httpStatusAndLocationHeader.getHttpStatus().value()); - softly.assertThat(response.header(LOCATION)).contains(httpStatusAndLocationHeader.getLocation()); - }); - } - - public void 러너_게시글이_성공적으로_리뷰_완료_상태인지_확인한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) { - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(httpStatusAndLocationHeader.getHttpStatus().value()); - softly.assertThat(response.header(LOCATION)).contains(httpStatusAndLocationHeader.getLocation()); - } - ); - } - - public void 러너_게시글_등록_성공을_검증한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) { - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(httpStatusAndLocationHeader.getHttpStatus().value()); - softly.assertThat(response.header(LOCATION)).contains(httpStatusAndLocationHeader.getLocation()); - } - ); - } - - public void 러너_게시글_등록_실패를_검증한다(final ErrorResponse 예상_에러_응답) { - final ErrorResponse 실제_에러_응답 = response.as(ErrorResponse.class); - - assertSoftly(softly -> { - softly.assertThat(실제_에러_응답.errorCode()).isEqualTo(예상_에러_응답.errorCode()); - softly.assertThat(실제_에러_응답.message()).isEqualTo(예상_에러_응답.message()); - }); - } - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadAssuredTest.java deleted file mode 100644 index 16de0c0f0..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadAssuredTest.java +++ /dev/null @@ -1,125 +0,0 @@ -package touch.baton.assure.runnerpost; - -import org.junit.jupiter.api.Test; -import org.springframework.data.domain.PageRequest; -import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.vo.ReviewStatus; - -import java.time.LocalDateTime; -import java.util.List; - -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.*; - -@SuppressWarnings("NonAsciiCharacters") -class RunnerPostReadAssuredTest extends AssuredTestConfig { - - @Test - void 러너_게시글_전체_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); - final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); - - final RunnerPost 디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); - final Long 디투_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(디투_러너_게시글_식별자값); - - final PageRequest 페이징_정보 = PageRequest.of(1, 10); - final RunnerPostResponse.Simple 디투_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답(디투_러너_게시글, 0, 디투_러너_게시글에_지원한_서포터_수, ReviewStatus.NOT_STARTED, List.of("자바", "스프링")); - - // when, then - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .리뷰_상태로_전체_러너_게시글_페이징을_조회한다(페이징_정보, ReviewStatus.NOT_STARTED) - - .서버_응답() - .전체_러너_게시글_페이징_조회_성공을_검증한다( - 러너_게시글_전체_Simple_페이징_응답(페이징_정보, List.of(디투_러너_게시글_Simple_응답)) - ); - } - - @Test - void 마이페이지_러너_게시글_페이징_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final Long 헤나_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); - - final RunnerPost 헤나_러너_게시글 = runnerPostRepository.getByRunnerPostId(헤나_러너_게시글_식별자값); - final Long 헤나_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(헤나_러너_게시글_식별자값); - - final PageRequest 페이징_정보 = PageRequest.of(1, 10); - - // when, then - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .마이페이지_러너_게시글_페이징을_조회한다(ReviewStatus.NOT_STARTED, 페이징_정보) - - .서버_응답() - .마이페이지_러너_게시글_페이징_조회_성공을_검증한다( - 마이페이지_러너_게시글_응답(페이징_정보, - List.of( - 마이페이지_러너_게시글_SimpleInMyPage_응답(헤나_러너_게시글, null, 0, 헤나_러너_게시글에_지원한_서포터_수, ReviewStatus.NOT_STARTED, List.of("자바", "스프링") - ) - )) - ); - } - - @Test - void 태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징을_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - - final Long 헤나_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); - - final RunnerPost 헤나_러너_게시글 = runnerPostRepository.getByRunnerPostId(헤나_러너_게시글_식별자값); - final Long 서포터_지원자_수 = runnerPostReadRepository.countApplicantByRunnerPostId(헤나_러너_게시글_식별자값); - - final PageRequest 페이징_정보 = PageRequest.of(1, 10); - - // when, then - final RunnerPostResponse.Simple 기대된_헤나_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답( - 헤나_러너_게시글, - 0, - 서포터_지원자_수, - ReviewStatus.NOT_STARTED, - List.of("자바", "스프링") - ); - - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징을_조회한다(페이징_정보, "자바", ReviewStatus.NOT_STARTED) - - .서버_응답() - .태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( - 러너_게시글_전체_Simple_페이징_응답(페이징_정보, List.of(기대된_헤나_러너_게시글_Simple_응답)) - ); - } - - private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 사용자_액세스_토큰) { - return RunnerPostAssuredCreateSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(사용자_액세스_토큰) - .러너가_러너_게시글을_작성한다( - 러너_게시글_생성_요청( - "테스트용_러너_게시글_제목", - List.of("자바", "스프링"), - "https://test-pull-request.com", - LocalDateTime.now().plusHours(100), - "테스트용_러너_게시글_구현_내용", - "테스트용_러너_게시글_궁금한_내용", - "테스트용_러너_게시글_참고_사항" - ) - ) - - .서버_응답() - .러너_게시글_생성_성공을_검증한다() - .생성한_러너_게시글의_식별자값을_반환한다(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedSupporterAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedSupporterAssuredTest.java deleted file mode 100644 index c93425542..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedSupporterAssuredTest.java +++ /dev/null @@ -1,185 +0,0 @@ -package touch.baton.assure.runnerpost; - -import org.junit.jupiter.api.Test; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpStatus; -import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; - -import java.time.LocalDateTime; -import java.util.List; - -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.*; - -@SuppressWarnings("NonAsciiCharacters") -class RunnerPostReadWithLoginedSupporterAssuredTest extends AssuredTestConfig { - - private final PageRequest 페이징_정보 = PageRequest.of(1, 10); - - @Test - void 로그인된_서포터는_서포터와_연관된_대기중인_러너_게시글_목록_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); - final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); - - // when - 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); - - final RunnerPost 리뷰_신청을_대기중인_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); - final Long 디투_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(디투_러너_게시글_식별자값); - - final RunnerPostResponse.ReferencedBySupporter 서포터가_리뷰를_지원한_대기중인_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( - 리뷰_신청을_대기중인_디투_러너_게시글, - List.of("자바", "스프링"), - 0, - 디투_러너_게시글에_지원한_서포터_수, - ReviewStatus.NOT_STARTED - ); - - // then - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .로그인한_서포터의_러너_게시글_페이징을_조회한다(ReviewStatus.NOT_STARTED, 페이징_정보) - - .서버_응답() - .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( - 서포터와_연관된_러너_게시글_페이징_응답(페이징_정보, List.of(서포터가_리뷰를_지원한_대기중인_러너_게시글_응답)) - ); - } - - @Test - void 로그인된_서포터의_진행중인_러너_게시글_목록_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); - final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); - - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); - final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); - - // when - 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); - 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 디투_러너_게시글_식별자값); - - final RunnerPost 리뷰_진행중인_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); - final Long 디투_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(디투_러너_게시글_식별자값); - - final RunnerPostResponse.ReferencedBySupporter 서포터가_리뷰를_진행중인_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( - 리뷰_진행중인_디투_러너_게시글, - List.of("자바", "스프링"), - 0, - 디투_러너_게시글에_지원한_서포터_수, - ReviewStatus.IN_PROGRESS - ); - - // then - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .로그인한_서포터의_러너_게시글_페이징을_조회한다(ReviewStatus.IN_PROGRESS, 페이징_정보) - - .서버_응답() - .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( - 서포터와_연관된_러너_게시글_페이징_응답(페이징_정보, List.of(서포터가_리뷰를_진행중인_러너_게시글_응답)) - ); - } - - @Test - void 로그인된_서포터의_완료된_러너_게시글_목록_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); - final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); - - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); - final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); - - // when - 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); - 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 디투_러너_게시글_식별자값); - 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); - - final RunnerPost 리뷰가_완료된_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); - final Long 디투_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(디투_러너_게시글_식별자값); - - final RunnerPostResponse.ReferencedBySupporter 서포터가_리뷰를_완료한_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( - 리뷰가_완료된_디투_러너_게시글, - List.of("자바", "스프링"), - 0, - 디투_러너_게시글에_지원한_서포터_수, - ReviewStatus.DONE - ); - - // then - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .로그인한_서포터의_러너_게시글_페이징을_조회한다(ReviewStatus.DONE, 페이징_정보) - - .서버_응답() - .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( - 서포터와_연관된_러너_게시글_페이징_응답(페이징_정보, List.of(서포터가_리뷰를_완료한_러너_게시글_응답)) - ); - } - - private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( - 러너_게시글_생성_요청( - "테스트용_러너_게시글_제목", - List.of("자바", "스프링"), - "https://test-pull-request.com", - LocalDateTime.now().plusHours(100), - "테스트용_러너_게시글_구현_내용", - "테스트용_러너_게시글_궁금한_내용", - "테스트용_러너_게시글_참고_사항" - ) - ) - - .서버_응답() - .러너_게시글_생성_성공을_검증한다() - .생성한_러너_게시글의_식별자값을_반환한다(); - } - - private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredCreateSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .서포터가_러너_게시글에_리뷰를_신청한다(디투_러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") - - .서버_응답() - .서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(디투_러너_게시글_식별자값); - } - - private void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터_헤나, final String 디투_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(디투_액세스_토큰) - .러너가_서포터를_선택한다(디투_러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터_헤나.getId())) - - .서버_응답() - .러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); - } - - private void 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(디투_러너_게시글_식별자값) - - .서버_응답() - .러너_게시글이_성공적으로_리뷰_완료_상태인지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithSupporterIdAndReviewStatusAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithSupporterIdAndReviewStatusAssuredTest.java deleted file mode 100644 index 63575e4f9..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithSupporterIdAndReviewStatusAssuredTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package touch.baton.assure.runnerpost; - -import org.junit.jupiter.api.Test; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpStatus; -import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; - -import java.time.LocalDateTime; -import java.util.List; - -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.*; - -@SuppressWarnings("NonAsciiCharacters") -class RunnerPostReadWithSupporterIdAndReviewStatusAssuredTest extends AssuredTestConfig { - - @Test - void 서포터가_리뷰_완료한_러너_게시글_페이징_조회에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); - final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); - - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); - final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); - - // when - 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); - 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 디투_러너_게시글_식별자값); - 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); - - final PageRequest 페이징_정보 = PageRequest.of(1, 10); - final RunnerPost 리뷰_신청을_대기중인_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); - final Long 디투_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(디투_러너_게시글_식별자값); - - final RunnerPostResponse.ReferencedBySupporter 서포터가_리뷰를_지원한_대기중인_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( - 리뷰_신청을_대기중인_디투_러너_게시글, - List.of("자바", "스프링"), - 0, - 디투_러너_게시글에_지원한_서포터_수, - ReviewStatus.DONE - ); - - // then - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .서포터와_연관된_러너_게시글_페이징을_조회한다(서포터_헤나.getId(), ReviewStatus.DONE, 페이징_정보) - - .서버_응답() - .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( - 서포터와_연관된_러너_게시글_페이징_응답(페이징_정보, List.of(서포터가_리뷰를_지원한_대기중인_러너_게시글_응답)) - ); - } - - private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( - 러너_게시글_생성_요청( - "테스트용_러너_게시글_제목", - List.of("자바", "스프링"), - "https://test-pull-request.com", - LocalDateTime.now().plusHours(100), - "테스트용_러너_게시글_구현_내용", - "테스트용_러너_게시글_궁금한_내용", - "테스트용_러너_게시글_참고_사항" - ) - ) - - .서버_응답() - .러너_게시글_생성_성공을_검증한다() - .생성한_러너_게시글의_식별자값을_반환한다(); - } - - private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredCreateSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .서포터가_러너_게시글에_리뷰를_신청한다(디투_러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") - - .서버_응답() - .서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(디투_러너_게시글_식별자값); - } - - private void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터_헤나, final String 디투_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(디투_액세스_토큰) - .러너가_서포터를_선택한다(디투_러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터_헤나.getId())) - - .서버_응답() - .러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); - } - - private void 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(디투_러너_게시글_식별자값) - - .서버_응답() - .러너_게시글이_성공적으로_리뷰_완료_상태인지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostCreateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostCreateAssuredTest.java similarity index 91% rename from backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostCreateAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostCreateAssuredTest.java index f40df0508..a11bb1ab2 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostCreateAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostCreateAssuredTest.java @@ -1,11 +1,12 @@ -package touch.baton.assure.runnerpost; +package touch.baton.assure.runnerpost.command; import org.junit.jupiter.api.Test; import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; import touch.baton.domain.common.response.ErrorResponse; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; import java.time.LocalDateTime; import java.util.List; @@ -18,7 +19,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 러너_게시글_등록이_성공한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -30,7 +31,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -42,7 +43,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 게시글_제목이_null이면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest(null, List.of("Java", "Spring"), @@ -54,7 +55,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -66,7 +67,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 게시글_태그가_null이면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", null, @@ -78,7 +79,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -90,7 +91,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 게시글_PR_URL이_null이면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -102,7 +103,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -114,7 +115,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 게시글_마감기한이_null이면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -126,7 +127,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -138,7 +139,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 게시글_마감기한이_현재보다_과거면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -150,7 +151,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -162,7 +163,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 구현_내용이_null이면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -174,7 +175,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -186,7 +187,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 궁금한_내용이_null이면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -198,7 +199,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -210,7 +211,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 참고_사항이_null이면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -222,7 +223,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) @@ -234,7 +235,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { @Test void 게시글_내용이_1000자_보다_길면_러너_게시글_등록_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 게시글_생성_요청 = new RunnerPostCreateRequest("코드 리뷰 해주세요.", List.of("Java", "Spring"), @@ -246,7 +247,7 @@ class RunnerPostCreateAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_등록_요청한다(게시글_생성_요청) diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostDeleteAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostDeleteAssuredTest.java similarity index 85% rename from backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostDeleteAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostDeleteAssuredTest.java index 84f291332..45fdf19b1 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostDeleteAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostDeleteAssuredTest.java @@ -1,20 +1,24 @@ -package touch.baton.assure.runnerpost; +package touch.baton.assure.runnerpost.command; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostDeleteSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostUpdateSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.supporter.Supporter; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; import java.time.LocalDateTime; import java.util.List; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.NO_CONTENT; -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.클라이언트_요청; @SuppressWarnings("NonAsciiCharacters") class RunnerPostDeleteAssuredTest extends AssuredTestConfig { @@ -22,11 +26,11 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { @Test void 리뷰가_대기중이고_리뷰_지원자가_없다면_러너의_게시글_식별자값으로_러너_게시글_삭제에_성공한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final Long 러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); // when, then - RunnerPostAssuredSupport + RunnerPostDeleteSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_식별자값으로_러너_게시글을_삭제한다(러너_게시글_식별자값) @@ -38,13 +42,13 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { @Test void 러너_게시글이_존재하지_않으면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); // when final Long 존재하지_않는_러너_게시글의_식별자값 = -1L; // then - RunnerPostAssuredSupport + RunnerPostDeleteSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_식별자값으로_러너_게시글을_삭제한다(존재하지_않는_러너_게시글의_식별자값) @@ -56,15 +60,15 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { @Test void 리뷰가_진행중인_상태라면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); // then - RunnerPostAssuredSupport + RunnerPostDeleteSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_식별자값으로_러너_게시글을_삭제한다(디투_러너_게시글_식별자값) @@ -76,11 +80,11 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { @Test void 리뷰가_완료된_상태라면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when @@ -89,7 +93,7 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); // then - RunnerPostAssuredSupport + RunnerPostDeleteSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_식별자값으로_러너_게시글을_삭제한다(디투_러너_게시글_식별자값) @@ -101,15 +105,15 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { @Test void 리뷰_요청_대기중인_상태이고_리뷰_지원자가_있는_상태라면_러너의_게시글_식별자값으로_러너_게시글_삭제에_실패한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); // then - RunnerPostAssuredSupport + RunnerPostDeleteSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_식별자값으로_러너_게시글을_삭제한다(디투_러너_게시글_식별자값) @@ -119,10 +123,10 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { } private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport + return RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( + .러너_게시글_등록_요청한다( 러너_게시글_생성_요청( "테스트용_러너_게시글_제목", List.of("자바", "스프링"), @@ -140,8 +144,7 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { } private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredCreateSupport - .클라이언트_요청() + 클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .서포터가_러너_게시글에_리뷰를_신청한다(디투_러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") @@ -150,7 +153,7 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { } private void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터_헤나, final String 디투_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport + RunnerPostUpdateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(디투_액세스_토큰) .러너가_서포터를_선택한다(디투_러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터_헤나.getId())) @@ -160,7 +163,7 @@ class RunnerPostDeleteAssuredTest extends AssuredTestConfig { } private void 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport + RunnerPostUpdateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(디투_러너_게시글_식별자값) diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostUpdateAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostUpdateAssuredTest.java similarity index 82% rename from backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostUpdateAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostUpdateAssuredTest.java index af8400993..2b533ffa1 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostUpdateAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/RunnerPostUpdateAssuredTest.java @@ -1,19 +1,22 @@ -package touch.baton.assure.runnerpost; +package touch.baton.assure.runnerpost.command; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostUpdateSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runnerpost.service.dto.RunnerPostUpdateRequest; -import touch.baton.domain.supporter.Supporter; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; import java.time.LocalDateTime; import java.util.List; -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.클라이언트_요청; @SuppressWarnings("NonAsciiCharacters") class RunnerPostUpdateAssuredTest extends AssuredTestConfig { @@ -21,11 +24,11 @@ class RunnerPostUpdateAssuredTest extends AssuredTestConfig { @Test void 러너가_서포터_목록에서_서포터를_선택할_수_있다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when @@ -34,7 +37,7 @@ class RunnerPostUpdateAssuredTest extends AssuredTestConfig { // then final RunnerPostUpdateRequest.SelectSupporter 러너의_서포터_선택_요청 = 러너의_서포터_선택_요청(서포터_헤나.getId()); - RunnerPostAssuredSupport + RunnerPostUpdateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(디투_액세스_토큰) .러너가_서포터를_선택한다(디투_러너_게시글_식별자값, 러너의_서포터_선택_요청) @@ -46,11 +49,11 @@ class RunnerPostUpdateAssuredTest extends AssuredTestConfig { @Test void 서포터_리뷰완료_후_리뷰상태를_완료로_변경할_수_있다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when @@ -58,7 +61,7 @@ class RunnerPostUpdateAssuredTest extends AssuredTestConfig { 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 디투_러너_게시글_식별자값); // when, then - RunnerPostAssuredSupport + RunnerPostUpdateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(디투_러너_게시글_식별자값) @@ -69,10 +72,10 @@ class RunnerPostUpdateAssuredTest extends AssuredTestConfig { private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport + return RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( + .러너_게시글_등록_요청한다( 러너_게시글_생성_요청( "테스트용_러너_게시글_제목", List.of("자바", "스프링"), @@ -90,8 +93,7 @@ class RunnerPostUpdateAssuredTest extends AssuredTestConfig { } private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredCreateSupport - .클라이언트_요청() + 클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .서포터가_러너_게시글에_리뷰를_신청한다(디투_러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") @@ -100,7 +102,7 @@ class RunnerPostUpdateAssuredTest extends AssuredTestConfig { } private void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터_헤나, final String 디투_액세스_토큰, final Long 디투_러너_게시글_식별자값) { - RunnerPostAssuredSupport + RunnerPostUpdateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(디투_액세스_토큰) .러너가_서포터를_선택한다(디투_러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터_헤나.getId())) diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/applicant/RunnerPostApplicantCreateAssuredTest.java similarity index 75% rename from backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateTest.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/command/applicant/RunnerPostApplicantCreateAssuredTest.java index 4339379dc..6e026ec93 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/applicant/RunnerPostApplicantCreateAssuredTest.java @@ -1,34 +1,37 @@ -package touch.baton.assure.runnerpost; +package touch.baton.assure.runnerpost.command.applicant; import org.junit.jupiter.api.Test; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport; +import touch.baton.assure.runnerpost.support.query.detail.RunnerPostDetailSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; -import touch.baton.domain.runnerpost.vo.ReviewStatus; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; import java.util.List; import static java.time.LocalDateTime.now; -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.러너_게시글_Detail_응답; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.NOT_STARTED; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.query.detail.RunnerPostDetailSupport.러너_게시글_Detail_응답; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.NOT_STARTED; @SuppressWarnings("NonAsciiCharacters") -class RunnerPostAssuredCreateTest extends AssuredTestConfig { +class RunnerPostApplicantCreateAssuredTest extends AssuredTestConfig { @Test void 러너가_러너_게시글을_생성하고_서포터가_러너_게시글에_리뷰를_신청한다() { - final String 에단_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ethanAuthCode()); - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 에단_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ethanAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final RunnerPostCreateRequest 러너_게시글_생성_요청 = 러너_게시글_생성_요청을_생성한다(); - final Long 에단의_러너_게시글_식별자값 = RunnerPostAssuredCreateSupport + final Long 에단의_러너_게시글_식별자값 = RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(에단_액세스_토큰) - .러너가_러너_게시글을_작성한다(러너_게시글_생성_요청) + .러너_게시글_등록_요청한다(러너_게시글_생성_요청) .서버_응답() .러너_게시글_생성_성공을_검증한다() @@ -38,7 +41,7 @@ class RunnerPostAssuredCreateTest extends AssuredTestConfig { final Runner 러너_에단 = runnerRepository.getBySocialId(에단의_소셜_아이디); final RunnerPostResponse.Detail 리뷰가_시작되지_않은_에단의_러너_게시글_Detail_응답 = 러너_게시글_Detail_응답을_생성한다(러너_에단, 러너_게시글_생성_요청, NOT_STARTED, 에단의_러너_게시글_식별자값, 1, 0L, false); - RunnerPostAssuredSupport + RunnerPostDetailSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(에단_액세스_토큰) .러너_게시글_식별자값으로_러너_게시글을_조회한다(에단의_러너_게시글_식별자값) @@ -46,7 +49,7 @@ class RunnerPostAssuredCreateTest extends AssuredTestConfig { .서버_응답() .러너_게시글_단건_조회_성공을_검증한다(리뷰가_시작되지_않은_에단의_러너_게시글_Detail_응답); - RunnerPostAssuredCreateSupport + RunnerPostApplicantCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .서포터가_러너_게시글에_리뷰를_신청한다(에단의_러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") diff --git a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterRunnerPostDeleteAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/applicant/RunnerPostApplicantDeleteAssuredTest.java similarity index 77% rename from backend/baton/src/test/java/touch/baton/assure/supporter/SupporterRunnerPostDeleteAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/command/applicant/RunnerPostApplicantDeleteAssuredTest.java index b1359bc0a..4493b071b 100644 --- a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterRunnerPostDeleteAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/command/applicant/RunnerPostApplicantDeleteAssuredTest.java @@ -1,32 +1,34 @@ -package touch.baton.assure.supporter; +package touch.baton.assure.runnerpost.command.applicant; import org.junit.jupiter.api.Test; import touch.baton.assure.common.HttpStatusAndLocationHeader; -import touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport; +import touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport; +import touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantDeleteAssuredSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; import java.time.LocalDateTime; import java.util.List; import static org.springframework.http.HttpStatus.NO_CONTENT; -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.클라이언트_요청; @SuppressWarnings("NonAsciiCharacters") -class SupporterRunnerPostDeleteAssuredTest extends AssuredTestConfig { +class RunnerPostApplicantDeleteAssuredTest extends AssuredTestConfig { @Test void 러너_게시글에_보낸_리뷰_제안을_취소한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); // then - SupporterRunnerPostAssuredSupport + RunnerPostApplicantDeleteAssuredSupport .클라이언트_요청() .액세스_토큰으로_로그인_한다(헤나_액세스_토큰) .서포터가_리뷰_제안을_취소한다(디투_러너_게시글_식별자값) @@ -36,10 +38,9 @@ class SupporterRunnerPostDeleteAssuredTest extends AssuredTestConfig { } private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport - .클라이언트_요청() + return 클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( + .러너_게시글_등록_요청한다( 러너_게시글_생성_요청( "테스트용_러너_게시글_제목", List.of("자바", "스프링"), @@ -57,7 +58,7 @@ class SupporterRunnerPostDeleteAssuredTest extends AssuredTestConfig { } private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 서포터_액세스_토큰, final Long 러너_게시글_식별자값) { - RunnerPostAssuredCreateSupport + RunnerPostApplicantCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(서포터_액세스_토큰) .서포터가_러너_게시글에_리뷰를_신청한다(러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/applicant/RunnerPostApplicantAssuredTest.java similarity index 71% rename from backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/query/applicant/RunnerPostApplicantAssuredTest.java index 31b1c3a81..8614a87db 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadWithLoginedAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/applicant/RunnerPostApplicantAssuredTest.java @@ -1,28 +1,31 @@ -package touch.baton.assure.runnerpost; +package touch.baton.assure.runnerpost.query.applicant; import org.junit.jupiter.api.Test; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport; +import touch.baton.assure.runnerpost.support.query.applicant.RunnerPostApplicantQuerySupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runnerpost.controller.response.SupporterRunnerPostResponse; -import touch.baton.domain.supporter.Supporter; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.query.controller.response.SupporterRunnerPostResponse; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.지원한_서포터_응답; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.지원한_서포터_응답_목록_응답; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.query.applicant.RunnerPostApplicantQuerySupport.지원한_서포터_목록_응답; +import static touch.baton.assure.runnerpost.support.query.applicant.RunnerPostApplicantQuerySupport.지원한_서포터_응답; @SuppressWarnings("NonAsciiCharacters") -class RunnerPostReadWithLoginedAssuredTest extends AssuredTestConfig { +class RunnerPostApplicantAssuredTest extends AssuredTestConfig { @Test void 러너의_게시글_식별자값으로_지원한_서포터_목록_조회에_성공한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); // when @@ -39,20 +42,20 @@ class RunnerPostReadWithLoginedAssuredTest extends AssuredTestConfig { ); // then - RunnerPostAssuredSupport + RunnerPostApplicantQuerySupport .클라이언트_요청() .액세스_토큰으로_로그인한다(디투_액세스_토큰) .러너_게시글_식별자값으로_지원한_서포터_목록을_조회한다(디투_러너_게시글_식별자값) .서버_응답() - .지원한_서포터_목록_조회_성공을_검증한다(지원한_서포터_응답_목록_응답(List.of(지원한_서포터_헤나_응답))); + .지원한_서포터_목록_조회_성공을_검증한다(지원한_서포터_목록_응답(List.of(지원한_서포터_헤나_응답))); } private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport + return RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( + .러너_게시글_등록_요청한다( 러너_게시글_생성_요청( "테스트용_러너_게시글_제목", List.of("자바", "스프링"), @@ -70,7 +73,7 @@ class RunnerPostReadWithLoginedAssuredTest extends AssuredTestConfig { } private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 서포터_액세스_토큰, final Long 러너_게시글_식별자값) { - RunnerPostAssuredCreateSupport + RunnerPostApplicantCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(서포터_액세스_토큰) .서포터가_러너_게시글에_리뷰를_신청한다(러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/runner/RunnerPostCountByRunnerAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/runner/RunnerPostCountByRunnerAssuredTest.java new file mode 100644 index 000000000..183eba4f8 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/runner/RunnerPostCountByRunnerAssuredTest.java @@ -0,0 +1,60 @@ +package touch.baton.assure.runnerpost.query.count.runner; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.query.count.runner.RunnerPostCountByRunnerSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.time.LocalDateTime; +import java.util.List; + +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.query.count.runner.RunnerPostCountByRunnerSupport.러너_게시글_개수_응답; + +@SuppressWarnings("NonAsciiCharacters") +class RunnerPostCountByRunnerAssuredTest extends AssuredTestConfig { + + @Test + void 로그인한_러너와_연관된_러너_게시글_개수_조회에_성공한다() { + // given + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + 러너_게시글_생성에_성공한다(디투_액세스_토큰); + 러너_게시글_생성에_성공한다(헤나_액세스_토큰); + + // when + final RunnerPostResponse.Count 기대된_러너_게시글_개수 = 러너_게시글_개수_응답(1); + + // then + RunnerPostCountByRunnerSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(디투_액세스_토큰) + .리뷰_상태로_로그인한_러너와_연관된_러너_게시글_개수를_조회한다(ReviewStatus.NOT_STARTED) + + .서버_응답() + .로그인한_러너와_연관된_러너_게시글_개수_조회_성공을_검증한다(기대된_러너_게시글_개수); + } + + private void 러너_게시글_생성에_성공한다(final String 액세스_토큰) { + RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .러너_게시글_등록_요청한다( + 러너_게시글_생성_요청( + "테스트용_러너_게시글_제목", + List.of("자바", "스프링"), + "https://test-pull-request.com", + LocalDateTime.now().plusHours(100), + "테스트용_러너_게시글_구현_내용", + "테스트용_러너_게시글_궁금한_내용", + "테스트용_러너_게시글_참고_사항" + ) + ) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/supporter/RunnerPostCountBySupporterAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/supporter/RunnerPostCountBySupporterAssuredTest.java new file mode 100644 index 000000000..94ee83049 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/count/supporter/RunnerPostCountBySupporterAssuredTest.java @@ -0,0 +1,188 @@ +package touch.baton.assure.runnerpost.query.count.supporter; + +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostUpdateSupport; +import touch.baton.assure.runnerpost.support.query.count.supporter.RunnerPostCountBySupporterSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.time.LocalDateTime; +import java.util.List; + +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.클라이언트_요청; +import static touch.baton.assure.runnerpost.support.query.count.supporter.RunnerPostCountBySupporterSupport.러너_게시글_개수_응답; + +@SuppressWarnings("NonAsciiCharacters") + class RunnerPostCountBySupporterAssuredTest extends AssuredTestConfig { + + @Test + void 로그인한_서포터가_지원했으면서_아직_시작하지_않은_러너_게시글_개수_조회에_성공한다() { + // given + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final String 서포터_에단_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ethanAuthCode()); + final Long 디투_러너_게시글_식별자_값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + + 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); + + 서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_에단_액세스_토큰, 디투_러너_게시글_식별자_값); + + // when + final RunnerPostResponse.Count 기대된_러너_게시글_개수 = 러너_게시글_개수_응답(1); + + // then + RunnerPostCountBySupporterSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(서포터_에단_액세스_토큰) + .리뷰_상태로_로그인한_서포터와_연관된_러너_게시글_개수를_조회한다(ReviewStatus.NOT_STARTED) + + .서버_응답() + .서포터와_연관된_러너_게시글_개수_조회_성공을_검증한다(기대된_러너_게시글_개수); + } + + @Test + void 로그인한_서포터가_리뷰_중인_러너_게시글_개수_조회에_성공한다() { + // given + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final String 서포터_에단_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ethanAuthCode()); + final SocialId 서포터_에단_소셜_아이디 = jwtTestManager.parseToSocialId(서포터_에단_액세스_토큰); + final Supporter 서포터_에단 = supporterRepository.getBySocialId(서포터_에단_소셜_아이디); + + final Long 디투_러너_게시글_식별자_값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + final Long 헤나_러너_게시글_식별자_값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); + + 서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_에단_액세스_토큰, 헤나_러너_게시글_식별자_값); + + 서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_에단_액세스_토큰, 디투_러너_게시글_식별자_값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_에단, 디투_액세스_토큰, 디투_러너_게시글_식별자_값); + + // when + final RunnerPostResponse.Count 기대된_러너_게시글_개수 = 러너_게시글_개수_응답(1); + + // then + RunnerPostCountBySupporterSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(서포터_에단_액세스_토큰) + .리뷰_상태로_로그인한_서포터와_연관된_러너_게시글_개수를_조회한다(ReviewStatus.IN_PROGRESS) + + .서버_응답() + .서포터와_연관된_러너_게시글_개수_조회_성공을_검증한다(기대된_러너_게시글_개수); + } + + @Test + void 로그인한_서포터가_완료한_러너_게시글_개수_조회에_성공한다() { + // given + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final String 서포터_에단_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ethanAuthCode()); + final SocialId 서포터_에단_소셜_아이디 = jwtTestManager.parseToSocialId(서포터_에단_액세스_토큰); + final Supporter 서포터_에단 = supporterRepository.getBySocialId(서포터_에단_소셜_아이디); + + final Long 디투_러너_게시글_식별자_값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + final Long 헤나_러너_게시글_식별자_값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); + + 서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_에단_액세스_토큰, 헤나_러너_게시글_식별자_값); + + 서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_에단_액세스_토큰, 디투_러너_게시글_식별자_값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_에단, 디투_액세스_토큰, 디투_러너_게시글_식별자_값); + 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(서포터_에단_액세스_토큰, 디투_러너_게시글_식별자_값); + + // when + final RunnerPostResponse.Count 기대된_러너_게시글_개수 = 러너_게시글_개수_응답(1); + + // then + RunnerPostCountBySupporterSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(서포터_에단_액세스_토큰) + .리뷰_상태로_로그인한_서포터와_연관된_러너_게시글_개수를_조회한다(ReviewStatus.DONE) + + .서버_응답() + .서포터와_연관된_러너_게시글_개수_조회_성공을_검증한다(기대된_러너_게시글_개수); + } + + @Test + void 타인이_서포터가_완료한_러너_게시글_개수_조회에_성공한다() { + // given + final String 러너_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final String 서포터_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ethanAuthCode()); + final SocialId 서포터_소셜_아이디 = jwtTestManager.parseToSocialId(서포터_액세스_토큰); + final Supporter 서포터 = supporterRepository.getBySocialId(서포터_소셜_아이디); + + final Long 러너_게시글_식별자_값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(러너_액세스_토큰); + + 서포터가_러너_게시글에_리뷰_신청을_성공한다(서포터_액세스_토큰, 러너_게시글_식별자_값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터, 러너_액세스_토큰, 러너_게시글_식별자_값); + 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(서포터_액세스_토큰, 러너_게시글_식별자_값); + + // when + final RunnerPostResponse.Count 기대된_러너_게시글_개수 = 러너_게시글_개수_응답(1); + + // then + RunnerPostCountBySupporterSupport + .클라이언트_요청() + .서포터_식별자_값으로_서포터가_완료한_러너_게시글_개수를_조회한다(서포터.getId()) + + .서버_응답() + .서포터와_연관된_러너_게시글_개수_조회_성공을_검증한다(기대된_러너_게시글_개수); + } + + private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 러너_액세스_토큰) { + return RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(러너_액세스_토큰) + .러너_게시글_등록_요청한다( + 러너_게시글_생성_요청( + "테스트용_러너_게시글_제목", + List.of("자바", "스프링"), + "https://test-pull-request.com", + LocalDateTime.now().plusHours(100), + "테스트용_러너_게시글_구현_내용", + "테스트용_러너_게시글_궁금한_내용", + "테스트용_러너_게시글_참고_사항" + ) + ) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다() + .생성한_러너_게시글의_식별자값을_반환한다(); + } + + private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 서포터_액세스_토큰, final Long 러너_게시글_식별자값) { + 클라이언트_요청() + .액세스_토큰으로_로그인한다(서포터_액세스_토큰) + .서포터가_러너_게시글에_리뷰를_신청한다(러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") + + .서버_응답() + .서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(러너_게시글_식별자값); + } + + private void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터, final String 러너_액세스_토큰, final Long 러너_게시글_식별자값) { + RunnerPostUpdateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(러너_액세스_토큰) + .러너가_서포터를_선택한다(러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터.getId())) + + .서버_응답() + .러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); + } + + private void 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(final String 서포터_액세스_토큰, final Long 러너_게시글_식별자값) { + RunnerPostUpdateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(서포터_액세스_토큰) + .서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(러너_게시글_식별자값) + + .서버_응답() + .러너_게시글이_성공적으로_리뷰_완료_상태인지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadByRunnerPostIdAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/detail/RunnerPostInRunnerPostDetailAssuredTest.java similarity index 81% rename from backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadByRunnerPostIdAssuredTest.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/query/detail/RunnerPostInRunnerPostDetailAssuredTest.java index 6de97d797..02dd2eb78 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostReadByRunnerPostIdAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/detail/RunnerPostInRunnerPostDetailAssuredTest.java @@ -1,27 +1,29 @@ -package touch.baton.assure.runnerpost; +package touch.baton.assure.runnerpost.query.detail; import org.junit.jupiter.api.Test; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.query.detail.RunnerPostDetailSupport; import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; import java.time.LocalDateTime; import java.util.List; -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.runnerpost.RunnerPostAssuredSupport.러너_게시글_Detail_응답; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.query.detail.RunnerPostDetailSupport.러너_게시글_Detail_응답; import static touch.baton.fixture.vo.WatchedCountFixture.watchedCount; @SuppressWarnings("NonAsciiCharacters") -class RunnerPostReadByRunnerPostIdAssuredTest extends AssuredTestConfig { +class RunnerPostInRunnerPostDetailAssuredTest extends AssuredTestConfig { @Test void 러너의_게시글_식별자값으로_러너_게시글_상세_정보_조회에_성공한다() { // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); final Runner 러너_헤나 = runnerRepository.getBySocialId(헤나_소셜_아이디); @@ -38,7 +40,7 @@ class RunnerPostReadByRunnerPostIdAssuredTest extends AssuredTestConfig { ); // when, then - RunnerPostAssuredSupport + RunnerPostDetailSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) .러너_게시글_식별자값으로_러너_게시글을_조회한다(헤나_러너_게시글_식별자값) @@ -48,10 +50,10 @@ class RunnerPostReadByRunnerPostIdAssuredTest extends AssuredTestConfig { } private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환에_성공한다(final String 헤나_액세스_토큰) { - return RunnerPostAssuredCreateSupport + return RunnerPostCreateSupport .클라이언트_요청() .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .러너가_러너_게시글을_작성한다( + .러너_게시글_등록_요청한다( 러너_게시글_생성_요청( "테스트용_러너_게시글_제목", List.of("자바", "스프링"), diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java new file mode 100644 index 000000000..a5295d87a --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java @@ -0,0 +1,262 @@ +package touch.baton.assure.runnerpost.query.page; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.runnerpost.support.RunnerPostPageSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.time.LocalDateTime; +import java.util.List; + +import static touch.baton.assure.runnerpost.support.RunnerPostPageSupport.러너_게시글_Simple_응답; +import static touch.baton.assure.runnerpost.support.RunnerPostPageSupport.러너_게시글_전체_Simple_페이징_응답; +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; + +@SuppressWarnings("NonAsciiCharacters") +class RunnerPostPageAssuredTest extends AssuredTestConfig { + + @Test + void 조건_없이_러너_게시글_첫_페이지_조회에_성공한다() { + // given + final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final Long 러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + + final RunnerPost 러너_게시글 = runnerPostRepository.getByRunnerPostId(러너_게시글_식별자값); + final long 서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.Simple 기대된_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답( + 러너_게시글, + 0, + 서포터_지원자_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .조건_없이_러너_게시글_첫_페이지를_조회한다(페이지_크기) + + .서버_응답() + .리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_전체_Simple_페이징_응답 + ); + } + + @Test + void 조건_없이_러너_게시글_중간_페이지_조회에_성공한다() { + // given + final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + + final Long 다음_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final Long 이전_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + + final RunnerPost 다음_페이지_러너_게시글 = runnerPostRepository.getByRunnerPostId(다음_페이지_러너_게시글_식별자값); + final long 다음_페이지_게시글_서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(다음_페이지_러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.Simple 기대된_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답( + 다음_페이지_러너_게시글, + 0, + 다음_페이지_게시글_서포터_지원자_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .조건_없이_러너_게시글_중간_페이지를_조회한다(이전_페이지_러너_게시글_식별자값, 페이지_크기) + + .서버_응답() + .리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_전체_Simple_페이징_응답 + ); + } + + @Test + void 리뷰_상태를_조건으로_러너_게시글_첫_페이지_조회에_성공한다() { + // given + final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final Long 러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + + final RunnerPost 러너_게시글 = runnerPostRepository.getByRunnerPostId(러너_게시글_식별자값); + final long 서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.Simple 기대된_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답( + 러너_게시글, + 0, + 서포터_지원자_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .리뷰_상태로_러너_게시글_첫_페이지를_조회한다(페이지_크기, ReviewStatus.NOT_STARTED) + + .서버_응답() + .리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_전체_Simple_페이징_응답 + ); + } + + @Test + void 리뷰_상태를_조건으로_러너_게시글_중간_페이지_조회에_성공한다() { + // given + final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + + final Long 다음_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final Long 이전_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + + final RunnerPost 다음_페이지_러너_게시글 = runnerPostRepository.getByRunnerPostId(다음_페이지_러너_게시글_식별자값); + final long 다음_페이지_게시글_서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(다음_페이지_러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.Simple 기대된_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답( + 다음_페이지_러너_게시글, + 0, + 다음_페이지_게시글_서포터_지원자_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .리뷰_상태로_러너_게시글_중간_페이지를_조회한다(이전_페이지_러너_게시글_식별자값, 페이지_크기, ReviewStatus.NOT_STARTED) + + .서버_응답() + .리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_전체_Simple_페이징_응답 + ); + } + + @Test + void 태그_이름과_리뷰_상태를_조건으로_러너_게시글_첫_페이지_조회에_성공한다() { + // given + final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final Long 러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + + final RunnerPost 러너_게시글 = runnerPostRepository.getByRunnerPostId(러너_게시글_식별자값); + final long 서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.Simple 기대된_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답( + 러너_게시글, + 0, + 서포터_지원자_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .태그_이름과_리뷰_상태를_조건으로_러너_게시글_첫_페이지를_조회한다(페이지_크기, "자바", ReviewStatus.NOT_STARTED) + + .서버_응답() + .태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_전체_Simple_페이징_응답 + ); + } + + @Test + void 태그_이름과_리뷰_상태를_조건으로_러너_게시글_중간_페이지_조회에_성공한다() { + // given + final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + + final Long 다음_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final Long 이전_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + + final RunnerPost 현재_페이지_러너_게시글 = runnerPostRepository.getByRunnerPostId(다음_페이지_러너_게시글_식별자값); + final long 현재_페이지_게시글_서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(다음_페이지_러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.Simple 기대된_러너_게시글_Simple_응답 = 러너_게시글_Simple_응답( + 현재_페이지_러너_게시글, + 0, + 현재_페이지_게시글_서포터_지원자_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .태그_이름과_리뷰_상태를_조건으로_러너_게시글_중간_페이지를_조회한다(이전_페이지_러너_게시글_식별자값, 페이지_크기, "자바", ReviewStatus.NOT_STARTED) + + .서버_응답() + .태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_전체_Simple_페이징_응답 + ); + } + + private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { + return RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너_게시글_등록_요청한다( + 러너_게시글_생성_요청( + "테스트용_러너_게시글_제목", + List.of("자바", "스프링"), + "https://test-pull-request.com", + LocalDateTime.now().plusHours(100), + "테스트용_러너_게시글_구현_내용", + "테스트용_러너_게시글_궁금한_내용", + "테스트용_러너_게시글_참고_사항" + ) + ) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다() + .생성한_러너_게시글의_식별자값을_반환한다(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/runner/RunnerPostPageRunnerAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/runner/RunnerPostPageRunnerAssuredTest.java new file mode 100644 index 000000000..683c98eb5 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/runner/RunnerPostPageRunnerAssuredTest.java @@ -0,0 +1,116 @@ +package touch.baton.assure.runnerpost.query.page.runner; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.query.page.runner.RunnerPostPageRunnerSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.time.LocalDateTime; +import java.util.List; + +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.query.page.runner.RunnerPostPageRunnerSupport.러너와_연관된_러너_게시글_응답; +import static touch.baton.assure.runnerpost.support.query.page.runner.RunnerPostPageRunnerSupport.러너와_연관된_러너_게시글_페이징_응답; + +@SuppressWarnings("NonAsciiCharacters") +class RunnerPostPageRunnerAssuredTest extends AssuredTestConfig { + + @Test + void 러너와_연관된_러너_게시글_첫_페이지_조회에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final Long 헤나_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); + + final RunnerPost 헤나_러너_게시글 = runnerPostRepository.getByRunnerPostId(헤나_러너_게시글_식별자값); + final Long 헤나_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(헤나_러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.SimpleByRunner 기대된_러너_게시글_Simple_응답 = 러너와_연관된_러너_게시글_응답( + 헤나_러너_게시글, + null, + 0, + 헤나_러너_게시글에_지원한_서포터_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_페이징_응답 = 러너와_연관된_러너_게시글_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageRunnerSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너와_연관된_러너_게시글_첫_페이지를_조회한다(ReviewStatus.NOT_STARTED, 페이지_크기) + + .서버_응답() + .러너와_연관된_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_페이징_응답 + ); + } + + @Test + void 러너와_연관된_러너_게시글_중간_페이지_조회에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final Long 헤나_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); + final Long 이전_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(헤나_액세스_토큰); + + final RunnerPost 헤나_러너_게시글 = runnerPostRepository.getByRunnerPostId(헤나_러너_게시글_식별자값); + final Long 헤나_러너_게시글에_지원한_서포터_수 = supporterRunnerPostRepository.getApplicantCountByRunnerPostId(헤나_러너_게시글_식별자값); + final int 페이지_크기 = 10; + + // when, then + final RunnerPostResponse.SimpleByRunner 기대된_러너_게시글_Simple_응답 = 러너와_연관된_러너_게시글_응답( + 헤나_러너_게시글, + null, + 0, + 헤나_러너_게시글에_지원한_서포터_수, + ReviewStatus.NOT_STARTED, + List.of("자바", "스프링") + ); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse 기대된_러너_게시글_페이징_응답 = 러너와_연관된_러너_게시글_페이징_응답( + List.of(기대된_러너_게시글_Simple_응답), + 기대된_페이징_정보 + ); + + RunnerPostPageRunnerSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너와_연관된_러너_게시글_중간_페이지를_조회한다(이전_러너_게시글_식별자값, ReviewStatus.NOT_STARTED, 페이지_크기) + + .서버_응답() + .러너와_연관된_러너_게시글_페이징_조회_성공을_검증한다( + 기대된_러너_게시글_페이징_응답 + ); + } + + private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 헤나_액세스_토큰) { + return RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .러너_게시글_등록_요청한다( + 러너_게시글_생성_요청( + "테스트용_러너_게시글_제목", + List.of("자바", "스프링"), + "https://test-pull-request.com", + LocalDateTime.now().plusHours(100), + "테스트용_러너_게시글_구현_내용", + "테스트용_러너_게시글_궁금한_내용", + "테스트용_러너_게시글_참고_사항" + ) + ) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다() + .생성한_러너_게시글의_식별자값을_반환한다(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/supporter/RunnerPostPageSupporterAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/supporter/RunnerPostPageSupporterAssuredTest.java new file mode 100644 index 000000000..d3e7df49c --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/supporter/RunnerPostPageSupporterAssuredTest.java @@ -0,0 +1,253 @@ +package touch.baton.assure.runnerpost.query.page.supporter; + +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.runnerpost.support.command.RunnerPostUpdateSupport; +import touch.baton.assure.runnerpost.support.query.page.supporter.RunnerPostPageSupporterSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.time.LocalDateTime; +import java.util.List; + +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.러너의_서포터_선택_요청; +import static touch.baton.assure.runnerpost.support.command.applicant.RunnerPostApplicantCreateSupport.클라이언트_요청; +import static touch.baton.assure.runnerpost.support.query.page.supporter.RunnerPostPageSupporterSupport.서포터와_연관된_러너_게시글_응답; +import static touch.baton.assure.runnerpost.support.query.page.supporter.RunnerPostPageSupporterSupport.서포터와_연관된_러너_게시글_페이징_응답; + +@SuppressWarnings("NonAsciiCharacters") +class RunnerPostPageSupporterAssuredTest extends AssuredTestConfig { + + @Test + void 로그인된_서포터는_서포터와_연관된_대기중인_러너_게시글_첫_페이지_조회에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + + // when + 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); + + final RunnerPost 리뷰_신청을_대기중인_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); + final long 디투_러너_게시글에_지원한_서포터_수 = runnerPostRepository.countApplicantByRunnerPostId(디투_러너_게시글_식별자값); + + final RunnerPostResponse.Simple 서포터가_리뷰를_지원한_대기중인_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( + 리뷰_신청을_대기중인_디투_러너_게시글, + List.of("자바", "스프링"), + 0, + 디투_러너_게시글에_지원한_서포터_수, + ReviewStatus.NOT_STARTED + ); + final int 페이지_크기 = 10; + final PageResponse 서포터가_리뷰를_지원한_대기중인_러너_게시글_페이징_응답 = 서포터와_연관된_러너_게시글_페이징_응답( + List.of(서포터가_리뷰를_지원한_대기중인_러너_게시글_응답), + PageResponse.PageInfo.last() + ); + + // then + RunnerPostPageSupporterSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .로그인한_서포터의_러너_게시글_첫_페이지를_조회한다(ReviewStatus.NOT_STARTED, 페이지_크기) + + .서버_응답() + .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( + 서포터가_리뷰를_지원한_대기중인_러너_게시글_페이징_응답 + ); + } + + @Test + void 로그인된_서포터는_서포터와_연관된_진행중인_러너_게시글_중간_페이지_조회에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); + final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); + + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + final Long 이전_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + + // when + 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 디투_러너_게시글_식별자값); + 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 이전_러너_게시글_식별자값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 이전_러너_게시글_식별자값); + + final RunnerPost 리뷰_진행중인_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); + final long 디투_러너_게시글에_지원한_서포터_수 = runnerPostRepository.countApplicantByRunnerPostId(디투_러너_게시글_식별자값); + + final RunnerPostResponse.Simple 서포터가_리뷰를_지원한_진행중인_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( + 리뷰_진행중인_디투_러너_게시글, + List.of("자바", "스프링"), + 0, + 디투_러너_게시글에_지원한_서포터_수, + ReviewStatus.IN_PROGRESS + ); + final int 페이지_크기 = 10; + final PageResponse 서포터가_리뷰를_지원한_진행중인_러너_게시글_페이징_응답 = 서포터와_연관된_러너_게시글_페이징_응답( + List.of(서포터가_리뷰를_지원한_진행중인_러너_게시글_응답), + PageResponse.PageInfo.last() + ); + + // then + RunnerPostPageSupporterSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .로그인한_서포터의_러너_게시글_중간_페이지를_조회한다(이전_러너_게시글_식별자값, ReviewStatus.IN_PROGRESS, 페이지_크기) + + .서버_응답() + .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( + 서포터가_리뷰를_지원한_진행중인_러너_게시글_페이징_응답 + ); + } + + @Test + void 서포터가_리뷰_완료한_러너_게시글_첫_페이지_조회에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); + final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); + + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + + // when + 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 디투_러너_게시글_식별자값); + 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); + + final RunnerPost 리뷰_신청을_대기중인_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); + final long 디투_러너_게시글에_지원한_서포터_수 = runnerPostRepository.countApplicantByRunnerPostId(디투_러너_게시글_식별자값); + + final RunnerPostResponse.Simple 서포터가_리뷰를_완료한_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( + 리뷰_신청을_대기중인_디투_러너_게시글, + List.of("자바", "스프링"), + 0, + 디투_러너_게시글에_지원한_서포터_수, + ReviewStatus.DONE + ); + final int 페이지_크기 = 10; + final PageResponse 서포터가_리뷰를_완료한_러너_게시글_페이징_응답 = 서포터와_연관된_러너_게시글_페이징_응답( + List.of(서포터가_리뷰를_완료한_러너_게시글_응답), + PageResponse.PageInfo.last() + ); + + // then + RunnerPostPageSupporterSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .서포터와_연관된_러너_게시글_첫_페이지를_조회한다(서포터_헤나.getId(), ReviewStatus.DONE, 페이지_크기) + + .서버_응답() + .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( + 서포터가_리뷰를_완료한_러너_게시글_페이징_응답 + ); + } + + @Test + void 서포터가_리뷰_완료한_러너_게시글_중간_페이지_조회에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); + final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); + + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final Long 디투_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + final Long 이전_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(디투_액세스_토큰); + + // when + 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 디투_러너_게시글_식별자값); + 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(헤나_액세스_토큰, 디투_러너_게시글_식별자값); + 서포터가_러너_게시글에_리뷰_신청을_성공한다(헤나_액세스_토큰, 이전_러너_게시글_식별자값); + 러너가_서포터의_리뷰_신청_선택에_성공한다(서포터_헤나, 디투_액세스_토큰, 이전_러너_게시글_식별자값); + 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(헤나_액세스_토큰, 이전_러너_게시글_식별자값); + + final RunnerPost 리뷰가_완료된_디투_러너_게시글 = runnerPostRepository.getByRunnerPostId(디투_러너_게시글_식별자값); + final long 디투_러너_게시글에_지원한_서포터_수 = runnerPostRepository.countApplicantByRunnerPostId(디투_러너_게시글_식별자값); + + final RunnerPostResponse.Simple 서포터가_리뷰를_완료한_러너_게시글_응답 = 서포터와_연관된_러너_게시글_응답( + 리뷰가_완료된_디투_러너_게시글, + List.of("자바", "스프링"), + 0, + 디투_러너_게시글에_지원한_서포터_수, + ReviewStatus.DONE + ); + final int 페이지_크기 = 10; + final PageResponse 서포터가_리뷰를_완료한_러너_게시글_페이징_응답 = 서포터와_연관된_러너_게시글_페이징_응답( + List.of(서포터가_리뷰를_완료한_러너_게시글_응답), + PageResponse.PageInfo.last() + ); + + // then + RunnerPostPageSupporterSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .서포터와_연관된_러너_게시글_중간_페이지를_조회한다(이전_러너_게시글_식별자값, 서포터_헤나.getId(), ReviewStatus.DONE, 페이지_크기) + + .서버_응답() + .서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다( + 서포터가_리뷰를_완료한_러너_게시글_페이징_응답 + ); + } + + private void 서포터가_러너_게시글에_리뷰_신청을_성공한다(final String 서포터_액세스_토큰, final Long 러너_게시글_식별자값) { + 클라이언트_요청() + .액세스_토큰으로_로그인한다(서포터_액세스_토큰) + .서포터가_러너_게시글에_리뷰를_신청한다(러너_게시글_식별자값, "안녕하세요. 서포터 헤나입니다.") + + .서버_응답() + .서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(러너_게시글_식별자값); + } + + private void 러너가_서포터의_리뷰_신청_선택에_성공한다(final Supporter 서포터_헤나, final String 디투_액세스_토큰, final Long 디투_러너_게시글_식별자값) { + RunnerPostUpdateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(디투_액세스_토큰) + .러너가_서포터를_선택한다(디투_러너_게시글_식별자값, 러너의_서포터_선택_요청(서포터_헤나.getId())) + + .서버_응답() + .러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); + } + + private void 서포터가_러너_게시글의_리뷰를_완료로_변경하는_것을_성공한다(final String 헤나_액세스_토큰, final Long 디투_러너_게시글_식별자값) { + RunnerPostUpdateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(헤나_액세스_토큰) + .서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(디투_러너_게시글_식별자값) + + .서버_응답() + .러너_게시글이_성공적으로_리뷰_완료_상태인지_확인한다(new HttpStatusAndLocationHeader(HttpStatus.NO_CONTENT, "/api/v1/posts/runner")); + } + + private Long 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(final String 러너_액세스_토큰) { + return RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(러너_액세스_토큰) + .러너_게시글_등록_요청한다( + 러너_게시글_생성_요청( + "테스트용_러너_게시글_제목", + List.of("자바", "스프링"), + "https://test-pull-request.com", + LocalDateTime.now().plusHours(100), + "테스트용_러너_게시글_구현_내용", + "테스트용_러너_게시글_궁금한_내용", + "테스트용_러너_게시글_참고_사항" + ) + ) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다() + .생성한_러너_게시글의_식별자값을_반환한다(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/RunnerPostPageSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/RunnerPostPageSupport.java new file mode 100644 index 000000000..6582f8dcb --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/RunnerPostPageSupport.java @@ -0,0 +1,179 @@ +package touch.baton.assure.runnerpost.support; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.QueryParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.query.controller.response.RunnerResponse; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostPageSupport { + private RunnerPostPageSupport() { + } + + public static RunnerPostPageBuilder 클라이언트_요청() { + return new RunnerPostPageBuilder(); + } + + public static RunnerPostResponse.Simple 러너_게시글_Simple_응답(final RunnerPost 러너_게시글, + final int 조회수, + final long 지원한_서포터_수, + final ReviewStatus 리뷰_상태, + final List 러너_게시글_태그_목록 + ) { + return new RunnerPostResponse.Simple( + 러너_게시글.getId(), + 러너_게시글.getTitle().getValue(), + 러너_게시글.getDeadline().getValue(), + 조회수, + 지원한_서포터_수, + 리뷰_상태.name(), + RunnerResponse.Simple.from(러너_게시글.getRunner()), + 러너_게시글_태그_목록 + ); + } + + public static PageResponse 러너_게시글_전체_Simple_페이징_응답( + final List 러너_게시글_목록, + final PageResponse.PageInfo 페이징_정보 + ) { + return new PageResponse<>(러너_게시글_목록, 페이징_정보); + } + + public static class RunnerPostPageBuilder { + + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostPageBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostPageBuilder 조건_없이_러너_게시글_첫_페이지를_조회한다(final int 페이지_크기) { + final Map queryParams = Map.of( + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageBuilder 조건_없이_러너_게시글_중간_페이지를_조회한다(final Long 이전_페이지_마지막_게시글_식별자값, final int 페이지_크기) { + final Map queryParams = Map.of( + "cursor", 이전_페이지_마지막_게시글_식별자값, + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageBuilder 리뷰_상태로_러너_게시글_첫_페이지를_조회한다(final int 페이지_크기, final ReviewStatus 리뷰_상태) { + final Map queryParams = Map.of( + "limit", 페이지_크기, + "reviewStatus", 리뷰_상태 + ); + + response = AssuredSupport.get("/api/v1/posts/runner", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageBuilder 리뷰_상태로_러너_게시글_중간_페이지를_조회한다(final Long 이전_페이지_마지막_게시글_식별자값, + final int 페이지_크기, + final ReviewStatus 리뷰_상태 + ) { + final Map queryParams = Map.of( + "cursor", 이전_페이지_마지막_게시글_식별자값, + "limit", 페이지_크기, + "reviewStatus", 리뷰_상태 + ); + + response = AssuredSupport.get("/api/v1/posts/runner", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageBuilder 태그_이름과_리뷰_상태를_조건으로_러너_게시글_첫_페이지를_조회한다(final int 페이지_크기, + final String 태그_이름, + final ReviewStatus 리뷰_상태 + ) { + final Map queryParams = Map.of( + "limit", 페이지_크기, + "tagName", 태그_이름, + "reviewStatus", 리뷰_상태 + ); + + response = AssuredSupport.get("/api/v1/posts/runner", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageBuilder 태그_이름과_리뷰_상태를_조건으로_러너_게시글_중간_페이지를_조회한다(final Long 이전_페이지_마지막_게시글_식별자값, + final int 페이지_크기, + final String 태그_이름, + final ReviewStatus 리뷰_상태 + ) { + final Map queryParams = Map.of( + "cursor", 이전_페이지_마지막_게시글_식별자값, + "limit", 페이지_크기, + "tagName", 태그_이름, + "reviewStatus", 리뷰_상태 + ); + + response = AssuredSupport.get("/api/v1/posts/runner", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageResponseBuilder 서버_응답() { + return new RunnerPostPageResponseBuilder(response); + } + + } + + public static class RunnerPostPageResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostPageResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + final PageResponse 러너_게시글_페이징_응답 + ) { + final PageResponse actual = this.response.as(new TypeRef<>() { + }); + + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual).isEqualTo(러너_게시글_페이징_응답); + } + ); + } + + public void 태그_이름과_리뷰_상태를_조건으로_러너_게시글_페이징_조회_성공을_검증한다( + final PageResponse 러너_게시글_페이징_응답 + ) { + final PageResponse actual = this.response.as(new TypeRef<>() { + }); + + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual).isEqualTo(러너_게시글_페이징_응답); + } + ); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostCreateSupport.java similarity index 53% rename from backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateSupport.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostCreateSupport.java index 573a75ab7..9f963615d 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/RunnerPostAssuredCreateSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostCreateSupport.java @@ -1,28 +1,27 @@ -package touch.baton.assure.runnerpost; +package touch.baton.assure.runnerpost.support.command; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; import org.springframework.http.HttpStatus; import touch.baton.assure.common.AssuredSupport; -import touch.baton.assure.common.PathParams; -import touch.baton.domain.runnerpost.service.dto.RunnerPostApplicantCreateRequest; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; +import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.domain.common.response.ErrorResponse; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; import java.time.LocalDateTime; import java.util.List; -import java.util.Map; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.springframework.http.HttpHeaders.LOCATION; @SuppressWarnings("NonAsciiCharacters") -public class RunnerPostAssuredCreateSupport { +public class RunnerPostCreateSupport { - private RunnerPostAssuredCreateSupport() { + private RunnerPostCreateSupport() { } - public static RunnerPostClientRequestBuilder 클라이언트_요청() { - return new RunnerPostClientRequestBuilder(); + public static RunnerPostCreateBuilder 클라이언트_요청() { + return new RunnerPostCreateBuilder(); } public static RunnerPostCreateRequest 러너_게시글_생성_요청(final String 러너_게시글_제목, @@ -36,45 +35,36 @@ private RunnerPostAssuredCreateSupport() { return new RunnerPostCreateRequest(러너_게시글_제목, 태그_목록, 풀_리퀘스트, 마감기한, 구현_내용, 궁금한_내용, 참고_사항); } - public static class RunnerPostClientRequestBuilder { + public static class RunnerPostCreateBuilder { private ExtractableResponse response; private String accessToken; - public RunnerPostClientRequestBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + public RunnerPostCreateBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { this.accessToken = 액세스_토큰; return this; } - - public RunnerPostClientRequestBuilder 러너가_러너_게시글을_작성한다(final RunnerPostCreateRequest request) { - response = AssuredSupport.post("/api/v1/posts/runner", accessToken, request); - return this; - } - - public RunnerPostClientRequestBuilder 서포터가_러너_게시글에_리뷰를_신청한다(final Long 러너_게시글_식별자값, final String 리뷰_지원_메시지) { - response = AssuredSupport.post("/api/v1/posts/runner/{runnerPostId}/application", - accessToken, - new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값)), - new RunnerPostApplicantCreateRequest(리뷰_지원_메시지) - ); + + public RunnerPostCreateBuilder 러너_게시글_등록_요청한다(final RunnerPostCreateRequest 게시글_생성_요청) { + response = AssuredSupport.post("/api/v1/posts/runner", accessToken, 게시글_생성_요청); return this; } - public RunnerPostServerResponseBuilder 서버_응답() { - return new RunnerPostServerResponseBuilder(response); + public RunnerPostCreateResponseBuilder 서버_응답() { + return new RunnerPostCreateResponseBuilder(response); } } - public static class RunnerPostServerResponseBuilder { + public static class RunnerPostCreateResponseBuilder { private final ExtractableResponse response; - public RunnerPostServerResponseBuilder(final ExtractableResponse response) { + public RunnerPostCreateResponseBuilder(final ExtractableResponse response) { this.response = response; } - public RunnerPostServerResponseBuilder 러너_게시글_생성_성공을_검증한다() { + public RunnerPostCreateResponseBuilder 러너_게시글_생성_성공을_검증한다() { assertSoftly(softly -> { softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); softly.assertThat(response.header(LOCATION)).startsWith("/api/v1/posts/runner/"); @@ -83,19 +73,27 @@ public RunnerPostServerResponseBuilder(final ExtractableResponse respo return this; } - public RunnerPostServerResponseBuilder 서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(final Long 러너_게시글_식별자값) { - assertSoftly(softly -> { - softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); - softly.assertThat(response.header(LOCATION)).startsWith("/api/v1/posts/runner/" + 러너_게시글_식별자값); - }); - - return this; - } - public Long 생성한_러너_게시글의_식별자값을_반환한다() { final String savedRunnerPostId = this.response.header(LOCATION).replaceFirst("/api/v1/posts/runner/", ""); return Long.parseLong(savedRunnerPostId); } + + public void 러너_게시글_등록_성공을_검증한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(httpStatusAndLocationHeader.getHttpStatus().value()); + softly.assertThat(response.header(LOCATION)).contains(httpStatusAndLocationHeader.getLocation()); + } + ); + } + + public void 러너_게시글_등록_실패를_검증한다(final ErrorResponse 예상_에러_응답) { + final ErrorResponse 실제_에러_응답 = response.as(ErrorResponse.class); + + assertSoftly(softly -> { + softly.assertThat(실제_에러_응답.errorCode()).isEqualTo(예상_에러_응답.errorCode()); + softly.assertThat(실제_에러_응답.message()).isEqualTo(예상_에러_응답.message()); + }); + } } } diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostDeleteSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostDeleteSupport.java new file mode 100644 index 000000000..62309a942 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostDeleteSupport.java @@ -0,0 +1,60 @@ +package touch.baton.assure.runnerpost.support.command; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.PathParams; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostDeleteSupport { + + private RunnerPostDeleteSupport() { + } + + public static RunnerPostDeleteBuilder 클라이언트_요청() { + return new RunnerPostDeleteBuilder(); + } + + public static class RunnerPostDeleteBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostDeleteBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostDeleteBuilder 러너_게시글_식별자값으로_러너_게시글을_삭제한다(final Long 러너_게시글_식별자값) { + response = AssuredSupport.delete("/api/v1/posts/runner/{runnerPostId}", accessToken, new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값))); + return this; + } + + public RunnerPostDeleteResponseBuilder 서버_응답() { + return new RunnerPostDeleteResponseBuilder(response); + } + } + + public static class RunnerPostDeleteResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostDeleteResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 러너_게시글_삭제_성공을_검증한다(final HttpStatus HTTP_STATUS) { + assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value()); + } + + public void 러너_게시글_삭제_실패를_검증한다(final HttpStatus HTTP_STATUS) { + assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value()); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostUpdateSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostUpdateSupport.java new file mode 100644 index 000000000..a3605bf13 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/RunnerPostUpdateSupport.java @@ -0,0 +1,81 @@ +package touch.baton.assure.runnerpost.support.command; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.HttpStatusAndLocationHeader; +import touch.baton.assure.common.PathParams; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; + +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.http.HttpHeaders.LOCATION; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostUpdateSupport { + + private RunnerPostUpdateSupport() { + } + + public static RunnerPostUpdateBuilder 클라이언트_요청() { + return new RunnerPostUpdateBuilder(); + } + + + public static class RunnerPostUpdateBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostUpdateBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostUpdateBuilder 서포터가_리뷰를_완료하고_리뷰완료_버튼을_누른다(final Long 게시글_식별자) { + response = AssuredSupport.patch("/api/v1/posts/runner/{runnerPostId}/done", accessToken, new PathParams(Map.of("runnerPostId", 게시글_식별자))); + return this; + } + + public RunnerPostUpdateBuilder 러너가_서포터를_선택한다(final Long 게시글_식별자값, + final RunnerPostUpdateRequest.SelectSupporter 서포터_선택_요청_정보 + ) { + response = AssuredSupport.patch("/api/v1/posts/runner/{runnerPostId}/supporters", + accessToken, + new PathParams(Map.of("runnerPostId", 게시글_식별자값)), + 서포터_선택_요청_정보 + ); + return this; + } + + public RunnerPostUpdateResponseBuilder 서버_응답() { + return new RunnerPostUpdateResponseBuilder(response); + } + } + + public static class RunnerPostUpdateResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostUpdateResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 러너_게시글이_성공적으로_리뷰_완료_상태인지_확인한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(httpStatusAndLocationHeader.getHttpStatus().value()); + softly.assertThat(response.header(LOCATION)).contains(httpStatusAndLocationHeader.getLocation()); + } + ); + } + + public void 러너_게시글에_서포터가_성공적으로_선택되었는지_확인한다(final HttpStatusAndLocationHeader httpStatusAndLocationHeader) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(httpStatusAndLocationHeader.getHttpStatus().value()); + softly.assertThat(response.header(LOCATION)).contains(httpStatusAndLocationHeader.getLocation()); + }); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/applicant/RunnerPostApplicantCreateSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/applicant/RunnerPostApplicantCreateSupport.java new file mode 100644 index 000000000..71c63cfbe --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/applicant/RunnerPostApplicantCreateSupport.java @@ -0,0 +1,78 @@ +package touch.baton.assure.runnerpost.support.command.applicant; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.PathParams; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostApplicantCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; + +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.http.HttpHeaders.LOCATION; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostApplicantCreateSupport { + + private RunnerPostApplicantCreateSupport() { + } + + public static RunnerPostApplicantCreateBuilder 클라이언트_요청() { + return new RunnerPostApplicantCreateBuilder(); + } + + public static RunnerPostUpdateRequest.SelectSupporter 러너의_서포터_선택_요청(final Long 러너가_선택한_서포터_식별자값) { + return new RunnerPostUpdateRequest.SelectSupporter(러너가_선택한_서포터_식별자값); + } + + public static class RunnerPostApplicantCreateBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostApplicantCreateBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostApplicantCreateBuilder 서포터가_러너_게시글에_리뷰를_신청한다(final Long 러너_게시글_식별자값, final String 리뷰_지원_메시지) { + response = AssuredSupport.post("/api/v1/posts/runner/{runnerPostId}/application", + accessToken, + new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값)), + new RunnerPostApplicantCreateRequest(리뷰_지원_메시지) + ); + return this; + } + + public RunnerPostApplicantCreateResponseBuilder 서버_응답() { + return new RunnerPostApplicantCreateResponseBuilder(response); + } + } + + public static class RunnerPostApplicantCreateResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostApplicantCreateResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public Long 생성한_러너_게시글의_식별자값을_반환한다() { + final String savedRunnerPostId = this.response.header(LOCATION).replaceFirst("/api/v1/posts/runner/", ""); + + return Long.parseLong(savedRunnerPostId); + } + + public RunnerPostApplicantCreateResponseBuilder 서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(final Long 러너_게시글_식별자값) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + softly.assertThat(response.header(LOCATION)).startsWith("/api/v1/posts/runner/" + 러너_게시글_식별자값); + }); + + return this; + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterRunnerPostAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/applicant/RunnerPostApplicantDeleteAssuredSupport.java similarity index 92% rename from backend/baton/src/test/java/touch/baton/assure/supporter/SupporterRunnerPostAssuredSupport.java rename to backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/applicant/RunnerPostApplicantDeleteAssuredSupport.java index 2227f5e4f..96da592fe 100644 --- a/backend/baton/src/test/java/touch/baton/assure/supporter/SupporterRunnerPostAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/command/applicant/RunnerPostApplicantDeleteAssuredSupport.java @@ -1,4 +1,4 @@ -package touch.baton.assure.supporter; +package touch.baton.assure.runnerpost.support.command.applicant; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; @@ -12,9 +12,9 @@ import static org.springframework.http.HttpHeaders.LOCATION; @SuppressWarnings("NonAsciiCharacters") -public class SupporterRunnerPostAssuredSupport { +public class RunnerPostApplicantDeleteAssuredSupport { - private SupporterRunnerPostAssuredSupport() { + private RunnerPostApplicantDeleteAssuredSupport() { } public static SupporterRunnerPostClientRequestBuilder 클라이언트_요청() { diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/applicant/RunnerPostApplicantQuerySupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/applicant/RunnerPostApplicantQuerySupport.java new file mode 100644 index 000000000..b4719ff3d --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/applicant/RunnerPostApplicantQuerySupport.java @@ -0,0 +1,117 @@ +package touch.baton.assure.runnerpost.support.query.applicant; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.PathParams; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.query.controller.response.SupporterRunnerPostResponse; +import touch.baton.domain.runnerpost.query.controller.response.SupporterRunnerPostResponses; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.http.HttpHeaders.LOCATION; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostApplicantQuerySupport { + + private RunnerPostApplicantQuerySupport() { + } + + public static RunnerPostApplicantQueryBuilder 클라이언트_요청() { + return new RunnerPostApplicantQueryBuilder(); + } + + public static SupporterRunnerPostResponses.Detail 지원한_서포터_목록_응답(final List 지원한_서포터_응답_목록) { + return SupporterRunnerPostResponses.Detail.from(지원한_서포터_응답_목록); + } + + public static SupporterRunnerPostResponse.Detail 지원한_서포터_응답(final Supporter 지원한_서포터, + final int 서포터의_리뷰수, + final String 지원한_서포터_어필_메시지, + final List 서포터_기술_태그_목록 + ) { + return new SupporterRunnerPostResponse.Detail( + 지원한_서포터.getId(), + 지원한_서포터.getMember().getMemberName().getValue(), + 지원한_서포터.getMember().getCompany().getValue(), + 서포터의_리뷰수, + 지원한_서포터.getMember().getImageUrl().getValue(), + 지원한_서포터_어필_메시지, + 서포터_기술_태그_목록 + ); + } + + public static class RunnerPostApplicantQueryBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostApplicantQueryBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostApplicantQueryBuilder 러너_게시글_식별자값으로_지원한_서포터_목록을_조회한다(final Long 러너_게시글_식별자값) { + response = AssuredSupport.get( + "/api/v1/posts/runner/{runnerPostId}/supporters", + accessToken, + new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값))); + return this; + } + + public RunnerPostApplicantQueryResponseBuilder 서버_응답() { + return new RunnerPostApplicantQueryResponseBuilder(response); + } + } + + public static class RunnerPostApplicantQueryResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostApplicantQueryResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public RunnerPostApplicantQueryResponseBuilder 러너_게시글_생성_성공을_검증한다() { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + softly.assertThat(response.header(LOCATION)).startsWith("/api/v1/posts/runner/"); + }); + + return this; + } + + public Long 생성한_러너_게시글의_식별자값을_반환한다() { + final String savedRunnerPostId = this.response.header(LOCATION).replaceFirst("/api/v1/posts/runner/", ""); + + return Long.parseLong(savedRunnerPostId); + } + + public RunnerPostApplicantQueryResponseBuilder 서포터가_러너_게시글에_리뷰_신청_성공을_검증한다(final Long 러너_게시글_식별자값) { + assertSoftly(softly -> { + softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + softly.assertThat(response.header(LOCATION)).startsWith("/api/v1/posts/runner/" + 러너_게시글_식별자값); + }); + + return this; + } + + public void 지원한_서포터_목록_조회_성공을_검증한다(final SupporterRunnerPostResponses.Detail 전체_러너_게시글_페이징_응답) { + final SupporterRunnerPostResponses.Detail actual = this.response.as(new TypeRef<>() { + + }); + + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual).isEqualTo(전체_러너_게시글_페이징_응답); + } + ); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/runner/RunnerPostCountByRunnerSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/runner/RunnerPostCountByRunnerSupport.java new file mode 100644 index 000000000..3f2dbaedc --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/runner/RunnerPostCountByRunnerSupport.java @@ -0,0 +1,68 @@ +package touch.baton.assure.runnerpost.support.query.count.runner; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.QueryParams; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostCountByRunnerSupport { + + private RunnerPostCountByRunnerSupport() { + } + + public static RunnerPostCountByRunnerBuilder 클라이언트_요청() { + return new RunnerPostCountByRunnerBuilder(); + } + + public static RunnerPostResponse.Count 러너_게시글_개수_응답(final long 게시글_개수) { + return new RunnerPostResponse.Count(게시글_개수); + } + + public static class RunnerPostCountByRunnerBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostCountByRunnerBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostCountByRunnerBuilder 리뷰_상태로_로그인한_러너와_연관된_러너_게시글_개수를_조회한다(final ReviewStatus 리뷰_상태) { + final Map queryParams = Map.of("reviewStatus", 리뷰_상태); + + response = AssuredSupport.get("/api/v1/posts/runner/me/runner/count", accessToken, new QueryParams(queryParams)); + return this; + } + + public RunnerPostCountByRunnerResponseBuilder 서버_응답() { + return new RunnerPostCountByRunnerResponseBuilder(response); + } + } + + public static class RunnerPostCountByRunnerResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostCountByRunnerResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 로그인한_러너와_연관된_러너_게시글_개수_조회_성공을_검증한다(final RunnerPostResponse.Count 러너_게시글_개수_응답) { + final RunnerPostResponse.Count actual = this.response.as(RunnerPostResponse.Count.class); + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual).isEqualTo(러너_게시글_개수_응답); + }); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/supporter/RunnerPostCountBySupporterSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/supporter/RunnerPostCountBySupporterSupport.java new file mode 100644 index 000000000..20c4cf08b --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/count/supporter/RunnerPostCountBySupporterSupport.java @@ -0,0 +1,78 @@ +package touch.baton.assure.runnerpost.support.query.count.supporter; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.QueryParams; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostCountBySupporterSupport { + + private RunnerPostCountBySupporterSupport() { + } + + public static RunnerPostCountBySupporterBuilder 클라이언트_요청() { + return new RunnerPostCountBySupporterBuilder(); + } + + public static RunnerPostResponse.Count 러너_게시글_개수_응답(final long 게시글_개수) { + return new RunnerPostResponse.Count(게시글_개수); + } + + public static class RunnerPostCountBySupporterBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostCountBySupporterBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostCountBySupporterBuilder 리뷰_상태로_로그인한_서포터와_연관된_러너_게시글_개수를_조회한다(final ReviewStatus 리뷰_상태) { + final Map queryParams = Map.of("reviewStatus", 리뷰_상태); + + response = AssuredSupport.get("/api/v1/posts/runner/me/supporter/count", accessToken, new QueryParams(queryParams)); + return this; + } + + public RunnerPostCountBySupporterBuilder 서포터_식별자_값으로_서포터가_완료한_러너_게시글_개수를_조회한다(final Long 서포터_식별자_값) { + final Map queryParams = Map.of( + "reviewStatus", ReviewStatus.DONE, + "supporterId", 서포터_식별자_값 + ); + + response = AssuredSupport.get("/api/v1/posts/runner/search/count", new QueryParams(queryParams)); + return this; + } + + public RunnerPostCountBySupporterResponseBuilder 서버_응답() { + return new RunnerPostCountBySupporterResponseBuilder(response); + } + } + + public static class RunnerPostCountBySupporterResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostCountBySupporterResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 서포터와_연관된_러너_게시글_개수_조회_성공을_검증한다(final RunnerPostResponse.Count 러너_게시글_개수_응답) { + final RunnerPostResponse.Count actual = this.response.as(RunnerPostResponse.Count.class); + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual).isEqualTo(러너_게시글_개수_응답); + }); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/detail/RunnerPostDetailSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/detail/RunnerPostDetailSupport.java new file mode 100644 index 000000000..a82a1c0ee --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/detail/RunnerPostDetailSupport.java @@ -0,0 +1,117 @@ +package touch.baton.assure.runnerpost.support.query.detail; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.PathParams; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.query.controller.response.RunnerResponse; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostDetailSupport { + + private RunnerPostDetailSupport() { + } + + public static RunnerPostDetailBuilder 클라이언트_요청() { + return new RunnerPostDetailBuilder(); + } + + public static RunnerPostResponse.Detail 러너_게시글_Detail_응답(final Long 러너_게시글_식별자값, + final String 제목, + final String 구현_내용, + final String 궁금한_내용, + final String 참고_사항, + final String 풀_리퀘스트, + final LocalDateTime 마감기한, + final int 조회수, + final long 서포터_지원자수, + final ReviewStatus 리뷰_상태, + final boolean 주인_여부, + final boolean 서포터_지원_여부, + final Runner 러너, + final List 태그_목록 + ) { + return new RunnerPostResponse.Detail( + 러너_게시글_식별자값, + 제목, + 구현_내용, + 궁금한_내용, + 참고_사항, + 풀_리퀘스트, + 마감기한, + 조회수, + 서포터_지원자수, + 리뷰_상태, + 주인_여부, + 서포터_지원_여부, + 태그_목록, + RunnerResponse.InRunnerPostDetail.from(러너) + ); + } + + public static class RunnerPostDetailBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostDetailBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostDetailBuilder 러너_게시글_식별자값으로_러너_게시글을_조회한다(final Long 러너_게시글_식별자값) { + response = AssuredSupport.get( + "/api/v1/posts/runner/{runnerPostId}", + accessToken, + new PathParams(Map.of("runnerPostId", 러너_게시글_식별자값))); + return this; + } + + public RunnerPostDetailResponseBuilder 서버_응답() { + return new RunnerPostDetailResponseBuilder(response); + } + } + + public static class RunnerPostDetailResponseBuilder { + + private final ExtractableResponse response; + + public void 러너_게시글_단건_조회_성공을_검증한다(final RunnerPostResponse.Detail 러너_게시글_응답) { + final RunnerPostResponse.Detail actual = this.response.as(RunnerPostResponse.Detail.class); + + assertSoftly(softly -> { + softly.assertThat(actual.runnerPostId()).isEqualTo(러너_게시글_응답.runnerPostId()); + softly.assertThat(actual.title()).isEqualTo(러너_게시글_응답.title()); + softly.assertThat(actual.implementedContents()).isEqualTo(러너_게시글_응답.implementedContents()); + softly.assertThat(actual.curiousContents()).isEqualTo(러너_게시글_응답.curiousContents()); + softly.assertThat(actual.postscriptContents()).isEqualTo(러너_게시글_응답.postscriptContents()); + softly.assertThat(actual.deadline()).isEqualToIgnoringSeconds(러너_게시글_응답.deadline()); + softly.assertThat(actual.watchedCount()).isEqualTo(러너_게시글_응답.watchedCount()); + softly.assertThat(actual.applicantCount()).isEqualTo(러너_게시글_응답.applicantCount()); + softly.assertThat(actual.reviewStatus()).isEqualTo(러너_게시글_응답.reviewStatus()); + softly.assertThat(actual.tags()).isEqualTo(러너_게시글_응답.tags()); + softly.assertThat(actual.deadline()).isEqualToIgnoringSeconds(러너_게시글_응답.deadline()); + softly.assertThat(actual.runnerProfile().name()).isEqualTo(러너_게시글_응답.runnerProfile().name()); + softly.assertThat(actual.runnerProfile().company()).isEqualTo(러너_게시글_응답.runnerProfile().company()); + softly.assertThat(actual.runnerProfile().imageUrl()).isEqualTo(러너_게시글_응답.runnerProfile().imageUrl()); + softly.assertThat(actual.runnerProfile().runnerId()).isEqualTo(러너_게시글_응답.runnerProfile().runnerId()); + softly.assertThat(actual.watchedCount()).isEqualTo(러너_게시글_응답.watchedCount()); + softly.assertThat(actual.runnerPostId()).isEqualTo(러너_게시글_응답.runnerPostId()); + } + ); + } + public RunnerPostDetailResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/runner/RunnerPostPageRunnerSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/runner/RunnerPostPageRunnerSupport.java new file mode 100644 index 000000000..9af7555e3 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/runner/RunnerPostPageRunnerSupport.java @@ -0,0 +1,118 @@ +package touch.baton.assure.runnerpost.support.query.page.runner; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.QueryParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostPageRunnerSupport { + + private RunnerPostPageRunnerSupport() { + } + + public static RunnerPostPageRunnerBuilder 클라이언트_요청() { + return new RunnerPostPageRunnerBuilder(); + } + + + public static RunnerPostResponse.SimpleByRunner 러너와_연관된_러너_게시글_응답(final RunnerPost 러너_게시글, + final Long 지원한_서포터_식별자값, + final int 조회수, + final long 지원한_서포터_수, + final ReviewStatus 리뷰_상태, + final List 러너_게시글_태그_목록 + ) { + return new RunnerPostResponse.SimpleByRunner( + 러너_게시글.getId(), + 러너_게시글.getTitle().getValue(), + 러너_게시글.getDeadline().getValue(), + 조회수, + 지원한_서포터_수, + 리뷰_상태.name(), + 러너_게시글.getIsReviewed().getValue(), + 지원한_서포터_식별자값, + 러너_게시글_태그_목록 + ); + } + + public static PageResponse 러너와_연관된_러너_게시글_페이징_응답( + final List 러너_게시글_목록, + final PageResponse.PageInfo 페이징_정보 + ) { + return new PageResponse<>(러너_게시글_목록, 페이징_정보); + } + + public static class RunnerPostPageRunnerBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostPageRunnerBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostPageRunnerBuilder 러너와_연관된_러너_게시글_첫_페이지를_조회한다(final ReviewStatus 리뷰_상태, final int 페이지_크기) { + final Map queryParams = Map.of( + "reviewStatus", 리뷰_상태, + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner/me/runner", accessToken, new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageRunnerBuilder 러너와_연관된_러너_게시글_중간_페이지를_조회한다(final Long 이전_페이지_마지막_게시글_식별자값, + final ReviewStatus 리뷰_상태, + final int 페이지_크기 + ) { + final Map queryParams = Map.of( + "cursor", 이전_페이지_마지막_게시글_식별자값, + "reviewStatus", 리뷰_상태, + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner/me/runner", accessToken, new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageRunnerResponseBuilder 서버_응답() { + return new RunnerPostPageRunnerResponseBuilder(response); + } + } + + public static class RunnerPostPageRunnerResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostPageRunnerResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 러너와_연관된_러너_게시글_페이징_조회_성공을_검증한다( + final PageResponse 마이페이지_러너_게시글_페이징_응답 + ) { + final PageResponse actual = this.response.as(new TypeRef<>() { + }); + + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual.data()).isEqualTo(마이페이지_러너_게시글_페이징_응답.data()); + } + ); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/supporter/RunnerPostPageSupporterSupport.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/supporter/RunnerPostPageSupporterSupport.java new file mode 100644 index 000000000..ad7002425 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/support/query/page/supporter/RunnerPostPageSupporterSupport.java @@ -0,0 +1,146 @@ +package touch.baton.assure.runnerpost.support.query.page.supporter; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.QueryParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.query.controller.response.RunnerResponse; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RunnerPostPageSupporterSupport { + + private RunnerPostPageSupporterSupport() { + } + + public static RunnerPostPageSupporterBuilder 클라이언트_요청() { + return new RunnerPostPageSupporterBuilder(); + } + + public static RunnerPostResponse.Simple 서포터와_연관된_러너_게시글_응답(final RunnerPost 러너_게시글, + final List 러너_게시글_태그_목록, + final int 조회수, + final long 러너_게시글에_지원한_서포터수, + final ReviewStatus 리뷰_상태 + ) { + return new RunnerPostResponse.Simple( + 러너_게시글.getId(), + 러너_게시글.getTitle().getValue(), + 러너_게시글.getDeadline().getValue(), + 조회수, + 러너_게시글에_지원한_서포터수, + 리뷰_상태.name(), + RunnerResponse.Simple.from(러너_게시글.getRunner()), + 러너_게시글_태그_목록 + ); + } + + public static PageResponse 서포터와_연관된_러너_게시글_페이징_응답( + final List 러너_게시글_목록, + final PageResponse.PageInfo 페이징_정보 + ) { + return new PageResponse<>(러너_게시글_목록, 페이징_정보); + } + + public static class RunnerPostPageSupporterBuilder { + + private ExtractableResponse response; + + private String accessToken; + + public RunnerPostPageSupporterBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { + this.accessToken = 액세스_토큰; + return this; + } + + public RunnerPostPageSupporterBuilder 로그인한_서포터의_러너_게시글_첫_페이지를_조회한다(final ReviewStatus 리뷰_진행_상태, + final int 페이지_크기 + ) { + final Map queryParams = Map.of( + "reviewStatus", 리뷰_진행_상태, + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner/me/supporter", accessToken, new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageSupporterBuilder 로그인한_서포터의_러너_게시글_중간_페이지를_조회한다(final Long 이전_페이지_마지막_게시글_식별자값, + final ReviewStatus 리뷰_진행_상태, + final int 페이지_크기 + ) { + final Map queryParams = Map.of( + "cursor", 이전_페이지_마지막_게시글_식별자값, + "reviewStatus", 리뷰_진행_상태, + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner/me/supporter", accessToken, new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageSupporterBuilder 서포터와_연관된_러너_게시글_첫_페이지를_조회한다(final Long 서포터_식별자값, + final ReviewStatus 리뷰_진행_상태, + final int 페이지_크기 + ) { + final Map queryParams = Map.of( + "supporterId", 서포터_식별자값, + "reviewStatus", 리뷰_진행_상태, + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner/search", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageSupporterBuilder 서포터와_연관된_러너_게시글_중간_페이지를_조회한다(final Long 이전_페이지_마지막_게시글_식별자값, + final Long 서포터_식별자값, + final ReviewStatus 리뷰_진행_상태, + final int 페이지_크기 + ) { + final Map queryParams = Map.of( + "cursor", 이전_페이지_마지막_게시글_식별자값, + "supporterId", 서포터_식별자값, + "reviewStatus", 리뷰_진행_상태, + "limit", 페이지_크기 + ); + + response = AssuredSupport.get("/api/v1/posts/runner/search", new QueryParams(queryParams)); + return this; + } + + public RunnerPostPageSupporterResponseBuilder 서버_응답() { + return new RunnerPostPageSupporterResponseBuilder(response); + } + } + + public static class RunnerPostPageSupporterResponseBuilder { + + private final ExtractableResponse response; + + public RunnerPostPageSupporterResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 서포터와_연관된_러너_게시글_페이징_조회_성공을_검증한다(final PageResponse 서포터와_연관된_러너_게시글_페이징_응답) { + final PageResponse actual = this.response.as(new TypeRef<>() { + }); + + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual).isEqualTo(서포터와_연관된_러너_게시글_페이징_응답); + } + ); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/tag/TagAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/tag/TagAssuredSupport.java deleted file mode 100644 index d95fef753..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/tag/TagAssuredSupport.java +++ /dev/null @@ -1,85 +0,0 @@ -package touch.baton.assure.tag; - -import io.restassured.common.mapper.TypeRef; -import io.restassured.response.ExtractableResponse; -import io.restassured.response.Response; -import org.springframework.http.HttpStatus; -import touch.baton.assure.common.AssuredSupport; -import touch.baton.assure.common.QueryParams; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.controller.response.TagSearchResponse; -import touch.baton.domain.tag.controller.response.TagSearchResponses; - -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.SoftAssertions.assertSoftly; - -@SuppressWarnings("NonAsciiCharacters") -class TagAssuredSupport { - - private TagAssuredSupport() { - } - - public static TagAssuredSupport.TagClientRequestBuilder 클라이언트_요청() { - return new TagAssuredSupport.TagClientRequestBuilder(); - } - - public static class TagClientRequestBuilder { - - private ExtractableResponse response; - - private String accessToken; - - public TagClientRequestBuilder 액세스_토큰으로_로그인한다(final String 액세스_토큰) { - this.accessToken = 액세스_토큰; - return this; - } - - public TagClientRequestBuilder 러너_게시글_등록_요청한다(final RunnerPostCreateRequest 게시글_생성_요청) { - response = AssuredSupport.post("/api/v1/posts/runner", accessToken, 게시글_생성_요청); - return this; - } - - public TagClientRequestBuilder 태그_이름을_오름차순으로_10개_검색한다(final String 태그_이름) { - response = AssuredSupport.get("/api/v1/tags/search", new QueryParams(Map.of("tagName", 태그_이름))); - return this; - } - - public TagServerResponseBuilder 서버_응답() { - return new TagServerResponseBuilder(response); - } - - } - - public static class TagServerResponseBuilder { - - private final ExtractableResponse response; - - public TagServerResponseBuilder(final ExtractableResponse response) { - this.response = response; - } - - public void 태그_검색_성공을_검증한다(final TagSearchResponses.Detail 검색된_태그_목록) { - final TagSearchResponses.Detail actual = this.response.as(new TypeRef<>() { - - }); - - assertSoftly(softly -> { - softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); - softly.assertThat(actual.data()).isEqualTo(검색된_태그_목록.data()); - } - ); - } - } - - public static TagSearchResponses.Detail 태그_검색_Detail_응답(final List 검색된_태그_목록) { - List 태그_목록_응답 = 검색된_태그_목록.stream() - .map(tag -> TagSearchResponse.TagResponse.from(tag)) - .toList(); - final TagSearchResponses.Detail 검색된_태그_목록_응답들 = TagSearchResponses.Detail.from(태그_목록_응답); - - return 검색된_태그_목록_응답들; - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/tag/TagReadAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/tag/TagReadAssuredTest.java deleted file mode 100644 index 812c068d5..000000000 --- a/backend/baton/src/test/java/touch/baton/assure/tag/TagReadAssuredTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package touch.baton.assure.tag; - -import org.junit.jupiter.api.Test; -import touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport; -import touch.baton.config.AssuredTestConfig; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.vo.TagReducedName; - -import java.time.LocalDateTime; -import java.util.List; - -import static touch.baton.assure.runnerpost.RunnerPostAssuredCreateSupport.러너_게시글_생성_요청; -import static touch.baton.assure.tag.TagAssuredSupport.태그_검색_Detail_응답; - -@SuppressWarnings("NonAsciiCharacters") -class TagReadAssuredTest extends AssuredTestConfig { - - @Test - void 태그_검색에_성공한다() { - // given - final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(MockAuthCodes.hyenaAuthCode()); - 러너_게시글_생성을_성공한다(헤나_액세스_토큰, List.of("java", "javascript", "script")); - final TagReducedName reducedName = TagReducedName.from("ja"); - final List 검색된_태그_목록 = tagRepository.readTagsByReducedName(reducedName); - - // when, then - TagAssuredSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(헤나_액세스_토큰) - .태그_이름을_오름차순으로_10개_검색한다("ja") - - .서버_응답() - .태그_검색_성공을_검증한다( - 태그_검색_Detail_응답(검색된_태그_목록) - ); - } - - public static void 러너_게시글_생성을_성공한다(final String 사용자_액세스_토큰, final List 태그_목록) { - RunnerPostAssuredCreateSupport - .클라이언트_요청() - .액세스_토큰으로_로그인한다(사용자_액세스_토큰) - .러너가_러너_게시글을_작성한다( - 러너_게시글_생성_요청( - "테스트용_러너_게시글_제목", - 태그_목록, - "https://test-pull-request.com", - LocalDateTime.now().plusHours(100), - "테스트용_러너_게시글_구현_내용", - "테스트용_러너_게시글_궁금한_내용", - "테스트용_러너_게시글_참고_사항" - ) - ) - - .서버_응답() - .러너_게시글_생성_성공을_검증한다(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/assure/tag/query/TagReadAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/tag/query/TagReadAssuredTest.java new file mode 100644 index 000000000..e491a3c53 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/tag/query/TagReadAssuredTest.java @@ -0,0 +1,59 @@ +package touch.baton.assure.tag.query; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport; +import touch.baton.assure.tag.support.query.TagQuerySupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.domain.tag.query.controller.response.TagSearchResponses; + +import java.time.LocalDateTime; +import java.util.List; + +import static touch.baton.assure.runnerpost.support.command.RunnerPostCreateSupport.러너_게시글_생성_요청; + + +@SuppressWarnings("NonAsciiCharacters") +class TagReadAssuredTest extends AssuredTestConfig { + + @Test + void 입력된_문자열로_태그_목록_검색에_성공한다() { + // given + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + 러너_게시글과_태그를_생성한다(헤나_액세스_토큰, List.of("java", "javascript", "script")); + + final TagReducedName 요청할_태그_이름 = TagReducedName.nullableInstance("ja"); + + // when, then + final List 검색된_태그_목록 = tagQueryRepository.findByTagReducedName(요청할_태그_이름, 10); + + TagQuerySupport + .클라이언트_요청() + .입력된_문자열로_태그_목록을_검색한다(요청할_태그_이름) + + .서버_응답() + .입력된_문자열로_태그_목록_검색_성공을_검증한다( + TagSearchResponses.Detail.from(검색된_태그_목록) + ); + } + + private void 러너_게시글과_태그를_생성한다(final String 액세스_토큰, final List 태그_목록) { + RunnerPostCreateSupport + .클라이언트_요청() + .액세스_토큰으로_로그인한다(액세스_토큰) + .러너_게시글_등록_요청한다(러너_게시글_생성_요청( + "테스트용_러너_게시글_제목", + 태그_목록, + "https://test-pull-request.com", + LocalDateTime.now().plusHours(100), + "테스트용_러너_게시글_구현_내용", + "테스트용_러너_게시글_궁금한_내용", + "테스트용_러너_게시글_참고_사항" + )) + + .서버_응답() + .러너_게시글_생성_성공을_검증한다(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/tag/support/query/TagQuerySupport.java b/backend/baton/src/test/java/touch/baton/assure/tag/support/query/TagQuerySupport.java new file mode 100644 index 000000000..115fbde5b --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/tag/support/query/TagQuerySupport.java @@ -0,0 +1,58 @@ +package touch.baton.assure.tag.support.query; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.QueryParams; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.domain.tag.query.controller.response.TagSearchResponses; + +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class TagQuerySupport { + + private TagQuerySupport() { + } + + public static TagQueryBuilder 클라이언트_요청() { + return new TagQueryBuilder(); + } + + public static class TagQueryBuilder { + + private ExtractableResponse response; + + public TagQueryBuilder 입력된_문자열로_태그_목록을_검색한다(final TagReducedName 요청할_태그_이름) { + response = AssuredSupport.get("/api/v1/tags/search", new QueryParams(Map.of("tagName", 요청할_태그_이름.getValue()))); + return this; + } + + public TagQueryResponseBuilder 서버_응답() { + return new TagQueryResponseBuilder(response); + } + } + + public static class TagQueryResponseBuilder { + + private final ExtractableResponse response; + + public TagQueryResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 입력된_문자열로_태그_목록_검색_성공을_검증한다(final TagSearchResponses.Detail 검색된_태그_목록) { + final TagSearchResponses.Detail actual = this.response.as(new TypeRef<>() { + }); + + assertSoftly(softly -> { + softly.assertThat(this.response.statusCode()).isEqualTo(HttpStatus.OK.value()); + softly.assertThat(actual.data()).isEqualTo(검색된_태그_목록.data()); + }); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java index 1cb5b3622..546ea6272 100644 --- a/backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java +++ b/backend/baton/src/test/java/touch/baton/common/schedule/RunnerPostDeadlineCheckSchedulerTest.java @@ -6,10 +6,10 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.Deadline; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -17,7 +17,7 @@ import java.time.LocalDateTime; import static org.assertj.core.api.Assertions.assertThat; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.OVERDUE; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.OVERDUE; import static touch.baton.fixture.vo.DeadlineFixture.deadline; class RunnerPostDeadlineCheckSchedulerTest extends ServiceTestConfig { diff --git a/backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostRepositoryTest.java b/backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostQueryRepositoryTest.java similarity index 88% rename from backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostQueryRepositoryTest.java index 912f5f77d..7c9d3fc0a 100644 --- a/backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/common/schedule/ScheduleRunnerPostQueryRepositoryTest.java @@ -6,12 +6,12 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.ReviewStatus; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -20,12 +20,10 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.DONE; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.NOT_STARTED; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.OVERDUE; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.*; import static touch.baton.fixture.vo.DeadlineFixture.deadline; -class ScheduleRunnerPostRepositoryTest extends RepositoryTestConfig { +class ScheduleRunnerPostQueryRepositoryTest extends RepositoryTestConfig { @Autowired private ScheduleRunnerPostRepository scheduleRunnerPostRepository; diff --git a/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java b/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java index 5f13b743b..417005ffa 100644 --- a/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java @@ -12,46 +12,45 @@ import org.springframework.test.context.TestExecutionListeners; import touch.baton.assure.common.JwtTestManager; import touch.baton.assure.common.OauthLoginTestManager; -import touch.baton.assure.repository.TestMemberRepository; +import touch.baton.assure.repository.TestMemberQueryRepository; +import touch.baton.assure.repository.TestNotificationCommandRepository; import touch.baton.assure.repository.TestRefreshTokenRepository; -import touch.baton.assure.repository.TestRunnerPostReadRepository; -import touch.baton.assure.repository.TestRunnerPostRepository; -import touch.baton.assure.repository.TestRunnerRepository; -import touch.baton.assure.repository.TestSupporterRepository; -import touch.baton.assure.repository.TestSupporterRunnerPostRepository; -import touch.baton.assure.repository.TestTagRepository; -import touch.baton.assure.repository.TestTechnicalTagRepository; +import touch.baton.assure.repository.TestRunnerPostQueryRepository; +import touch.baton.assure.repository.TestRunnerQueryRepository; +import touch.baton.assure.repository.TestSupporterQueryRepository; +import touch.baton.assure.repository.TestSupporterRunnerPostQueryRepository; +import touch.baton.assure.repository.TestTagQuerydslRepository; import touch.baton.config.converter.ConverterConfig; -import touch.baton.config.infra.auth.MockAuthTestConfig; +import touch.baton.config.infra.auth.MockBeanAuthTestConfig; import touch.baton.config.infra.github.MockGithubBranchServiceConfig; @ActiveProfiles("test") -@Import({JpaConfig.class, ConverterConfig.class, PageableTestConfig.class, MockAuthTestConfig.class, MockGithubBranchServiceConfig.class, JwtTestManager.class}) +@Import({JpaConfig.class, QuerydslConfig.class, ConverterConfig.class, PageableTestConfig.class, MockBeanAuthTestConfig.class, MockGithubBranchServiceConfig.class, JwtTestManager.class}) @TestExecutionListeners(value = AssuredTestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public abstract class AssuredTestConfig { @Autowired - protected TestMemberRepository memberRepository; + protected TestMemberQueryRepository memberRepository; @Autowired - protected TestRunnerRepository runnerRepository; + protected TestRunnerQueryRepository runnerRepository; @Autowired - protected TestSupporterRepository supporterRepository; + protected TestSupporterQueryRepository supporterRepository; @Autowired - protected TestRunnerPostRepository runnerPostRepository; + protected TestRunnerPostQueryRepository runnerPostRepository; @Autowired - protected TestRunnerPostReadRepository runnerPostReadRepository; + protected TestSupporterRunnerPostQueryRepository supporterRunnerPostRepository; @Autowired - protected TestSupporterRunnerPostRepository supporterRunnerPostRepository; + protected TestTagQuerydslRepository tagQueryRepository; @Autowired - protected TestTagRepository tagRepository; + protected TestNotificationCommandRepository notificationCommandRepository; @Autowired protected TestRefreshTokenRepository refreshTokenRepository; diff --git a/backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java b/backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java new file mode 100644 index 000000000..a06536c83 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java @@ -0,0 +1,37 @@ +package touch.baton.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import touch.baton.domain.notification.query.repository.NotificationQuerydslRepository; +import touch.baton.domain.runnerpost.query.repository.RunnerPostPageRepository; +import touch.baton.domain.tag.query.repository.TagQuerydslRepository; + +@TestConfiguration +public class QueryDslRepositoryTestConfig { + + @PersistenceContext + private EntityManager em; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(em); + } + + @Bean + public RunnerPostPageRepository runnerPostPageRepository() { + return new RunnerPostPageRepository(jpaQueryFactory()); + } + + @Bean + public NotificationQuerydslRepository notificationQuerydslRepository() { + return new NotificationQuerydslRepository(jpaQueryFactory()); + } + + @Bean + public TagQuerydslRepository tagQuerydslRepository() { + return new TagQuerydslRepository(jpaQueryFactory()); + } +} diff --git a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java index a366cfec7..67728ef28 100644 --- a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java @@ -1,9 +1,89 @@ package touch.baton.config; +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.Tag; +import touch.baton.fixture.domain.NotificationFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.RunnerPostTagFixture; +import touch.baton.fixture.domain.SupporterFixture; +import touch.baton.fixture.domain.SupporterRunnerPostFixture; +import touch.baton.fixture.domain.TagFixture; -@Import(JpaConfig.class) +import java.time.LocalDateTime; + +import static touch.baton.fixture.vo.DeadlineFixture.deadline; +import static touch.baton.fixture.vo.TagNameFixture.tagName; + +@Import({JpaConfig.class, QueryDslRepositoryTestConfig.class}) @DataJpaTest public abstract class RepositoryTestConfig { + + @Autowired + protected EntityManager em; + + protected Member persistMember(final Member member) { + em.persist(member); + return member; + } + + protected Runner persistRunner(final Member member) { + em.persist(member); + final Runner runner = RunnerFixture.createRunner(member); + em.persist(runner); + return runner; + } + + protected Supporter persistSupporter(final Member member) { + em.persist(member); + final Supporter supporter = SupporterFixture.create(member); + em.persist(supporter); + return supporter; + } + + protected RunnerPost persistRunnerPost(final Runner runner) { + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(100))); + em.persist(runnerPost); + return runnerPost; + } + + protected SupporterRunnerPost persistApplicant(final Supporter supporter, final RunnerPost runnerPost) { + final SupporterRunnerPost applicant = SupporterRunnerPostFixture.create(runnerPost, supporter); + em.persist(applicant); + return applicant; + } + + protected void persistAssignSupporter(final Supporter supporter, final RunnerPost runnerPost) { + runnerPost.assignSupporter(supporter); + em.persist(runnerPost); + } + + protected Tag persistTag(final String tagName) { + final Tag tag = TagFixture.create(tagName(tagName)); + em.persist(tag); + return tag; + } + + protected RunnerPostTag persistRunnerPostTag(final RunnerPost runnerPost, final Tag tag) { + final RunnerPostTag runnerPostTag = RunnerPostTagFixture.create(runnerPost, tag); + em.persist(runnerPostTag); + return runnerPostTag; + } + + protected Notification persistNotification(final Member targetMember, final NotificationReferencedId notificationReferencedId) { + final Notification notification = NotificationFixture.create(targetMember, notificationReferencedId); + em.persist(notification); + return notification; + } } diff --git a/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java b/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java index 0b2faed47..42ccfa96f 100644 --- a/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java @@ -3,32 +3,46 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; -import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -import org.springframework.data.web.PageableHandlerMethodArgumentResolver; -import org.springframework.format.support.FormattingConversionService; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.restdocs.ManualRestDocumentation; import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import touch.baton.config.converter.ConverterConfig; -import touch.baton.config.converter.OauthTypeConverter; -import touch.baton.config.converter.ReviewStatusConverter; -import touch.baton.domain.oauth.controller.resolver.AuthMemberPrincipalArgumentResolver; -import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipalArgumentResolver; -import touch.baton.domain.oauth.controller.resolver.AuthSupporterPrincipalArgumentResolver; -import touch.baton.domain.oauth.repository.OauthMemberRepository; -import touch.baton.domain.oauth.repository.OauthRunnerRepository; -import touch.baton.domain.oauth.repository.OauthSupporterRepository; +import org.springframework.web.context.WebApplicationContext; +import touch.baton.domain.member.command.controller.MemberBranchController; +import touch.baton.domain.member.command.controller.RunnerCommandController; +import touch.baton.domain.member.command.controller.SupporterCommandController; +import touch.baton.domain.member.command.service.GithubBranchManageable; +import touch.baton.domain.member.command.service.RunnerCommandService; +import touch.baton.domain.member.command.service.SupporterCommandService; +import touch.baton.domain.member.query.controller.MemberQueryController; +import touch.baton.domain.member.query.controller.RunnerQueryController; +import touch.baton.domain.member.query.controller.SupporterQueryController; +import touch.baton.domain.member.query.service.RunnerQueryService; +import touch.baton.domain.member.query.service.SupporterQueryService; +import touch.baton.domain.notification.command.controller.NotificationCommandController; +import touch.baton.domain.notification.command.service.NotificationCommandService; +import touch.baton.domain.notification.query.controller.NotificationQueryController; +import touch.baton.domain.notification.query.service.NotificationQueryService; +import touch.baton.domain.oauth.command.controller.OauthCommandController; +import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; +import touch.baton.domain.oauth.command.service.OauthCommandService; +import touch.baton.domain.runnerpost.command.controller.RunnerPostCommandController; +import touch.baton.domain.runnerpost.command.service.RunnerPostCommandService; +import touch.baton.domain.runnerpost.query.controller.RunnerPostQueryController; +import touch.baton.domain.runnerpost.query.service.RunnerPostQueryService; +import touch.baton.domain.tag.query.controller.TagQueryController; +import touch.baton.domain.tag.query.service.TagQueryService; import touch.baton.infra.auth.jwt.JwtDecoder; import java.util.UUID; @@ -40,14 +54,24 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; -@ExtendWith(RestDocumentationExtension.class) -@Import({RestDocsResultConfig.class, ConverterConfig.class}) +@WebMvcTest({ + RunnerQueryController.class, + RunnerCommandController.class, + SupporterQueryController.class, + SupporterCommandController.class, + MemberQueryController.class, + RunnerPostCommandController.class, + RunnerPostQueryController.class, + TagQueryController.class, + MemberBranchController.class, + OauthCommandController.class, + NotificationCommandController.class, + NotificationQueryController.class +}) +@Import({RestDocsResultConfig.class}) public abstract class RestdocsConfig { protected MockMvc mockMvc; - protected AuthMemberPrincipalArgumentResolver authMemberPrincipalArgumentResolver; - protected AuthRunnerPrincipalArgumentResolver authRunnerPrincipalArgumentResolver; - protected AuthSupporterPrincipalArgumentResolver authSupporterPrincipalArgumentResolver; @Autowired protected RestDocumentationResultHandler restDocs; @@ -62,38 +86,51 @@ public abstract class RestdocsConfig { protected JwtDecoder jwtDecoder; @MockBean - protected OauthMemberRepository oauthMemberRepository; + protected OauthMemberCommandRepository oauthMemberCommandRepository; @MockBean - protected OauthRunnerRepository oauthRunnerRepository; + protected OauthRunnerCommandRepository oauthRunnerCommandRepository; @MockBean - protected OauthSupporterRepository oauthSupporterRepository; + protected OauthSupporterCommandRepository oauthSupporterCommandRepository; - @Autowired - protected MappingJackson2HttpMessageConverter jackson2HttpMessageConverter; + @MockBean + protected GithubBranchManageable githubBranchManageable; - @Autowired - protected Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder; - - protected void restdocsSetUp(final Object controller) { - authMemberPrincipalArgumentResolver = new AuthMemberPrincipalArgumentResolver(jwtDecoder, oauthMemberRepository); - authRunnerPrincipalArgumentResolver = new AuthRunnerPrincipalArgumentResolver(jwtDecoder, oauthRunnerRepository); - authSupporterPrincipalArgumentResolver = new AuthSupporterPrincipalArgumentResolver(jwtDecoder, oauthSupporterRepository); - - final FormattingConversionService formattingConversionService = new FormattingConversionService(); - formattingConversionService.addConverter(new OauthTypeConverter()); - formattingConversionService.addConverter(new ReviewStatusConverter()); - - this.mockMvc = MockMvcBuilders.standaloneSetup(controller) - .setCustomArgumentResolvers( - authMemberPrincipalArgumentResolver, - authRunnerPrincipalArgumentResolver, - authSupporterPrincipalArgumentResolver, - new PageableHandlerMethodArgumentResolver()) + @MockBean + protected OauthCommandService oauthCommandService; + + @MockBean + protected RunnerPostQueryService runnerPostQueryService; + + @MockBean + protected RunnerPostCommandService runnerPostCommandService; + + @MockBean + protected RunnerQueryService runnerQueryService; + + @MockBean + protected RunnerCommandService runnerCommandService; + + @MockBean + protected SupporterQueryService supporterQueryService; + + @MockBean + protected SupporterCommandService supporterCommandService; + + @MockBean + protected TagQueryService tagQueryService; + + @MockBean + protected NotificationQueryService notificationQueryService; + + @MockBean + protected NotificationCommandService notificationCommandService; + + @BeforeEach + void restdocsSetUp(final WebApplicationContext webApplicationContext) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .apply(documentationConfiguration(restDocumentation)) - .setConversionService(formattingConversionService) - .setMessageConverters(jackson2HttpMessageConverter) .alwaysDo(restDocs) .build(); } diff --git a/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java b/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java index e9aab1534..647434219 100644 --- a/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java @@ -1,54 +1,82 @@ package touch.baton.config; import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.domain.feedback.repository.SupporterFeedbackRepository; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runnerpost.repository.RunnerPostReadRepository; -import touch.baton.domain.runnerpost.repository.RunnerPostRepository; -import touch.baton.domain.supporter.repository.SupporterRepository; -import touch.baton.domain.supporter.repository.SupporterRunnerPostRepository; -import touch.baton.domain.tag.repository.RunnerPostTagRepository; -import touch.baton.domain.tag.repository.TagRepository; -import touch.baton.domain.technicaltag.repository.RunnerTechnicalTagRepository; -import touch.baton.domain.technicaltag.repository.SupporterTechnicalTagRepository; -import touch.baton.domain.technicaltag.repository.TechnicalTagRepository; +import org.springframework.context.ApplicationEventPublisher; +import touch.baton.domain.feedback.command.repository.SupporterFeedbackCommandRepository; +import touch.baton.domain.member.command.repository.MemberCommandRepository; +import touch.baton.domain.member.command.repository.SupporterCommandRepository; +import touch.baton.domain.member.command.repository.SupporterRunnerPostCommandRepository; +import touch.baton.domain.member.query.repository.RunnerQueryRepository; +import touch.baton.domain.member.query.repository.SupporterQueryRepository; +import touch.baton.domain.member.query.repository.SupporterRunnerPostQueryRepository; +import touch.baton.domain.notification.command.repository.NotificationCommandRepository; +import touch.baton.domain.notification.query.repository.NotificationQuerydslRepository; +import touch.baton.domain.runnerpost.command.repository.RunnerPostCommandRepository; +import touch.baton.domain.runnerpost.query.repository.RunnerPostPageRepository; +import touch.baton.domain.runnerpost.query.repository.RunnerPostQueryRepository; +import touch.baton.domain.tag.command.repository.TagCommandRepository; +import touch.baton.domain.tag.query.repository.RunnerPostTagQueryRepository; +import touch.baton.domain.tag.query.repository.TagQuerydslRepository; +import touch.baton.domain.technicaltag.command.repository.RunnerTechnicalTagCommandRepository; +import touch.baton.domain.technicaltag.command.repository.SupporterTechnicalTagCommandRepository; +import touch.baton.domain.technicaltag.query.repository.TechnicalTagQueryRepository; public abstract class ServiceTestConfig extends RepositoryTestConfig { @Autowired - protected MemberRepository memberRepository; + protected MemberCommandRepository memberCommandRepository; @Autowired - protected RunnerRepository runnerRepository; + protected RunnerQueryRepository runnerQueryRepository; @Autowired - protected SupporterRepository supporterRepository; + protected SupporterQueryRepository supporterQueryRepository; @Autowired - protected RunnerPostRepository runnerPostRepository; + protected RunnerPostQueryRepository runnerPostQueryRepository; @Autowired - protected RunnerPostReadRepository runnerPostReadRepository; + protected RunnerPostPageRepository runnerPostPageRepository; @Autowired - protected SupporterRunnerPostRepository supporterRunnerPostRepository; + protected SupporterRunnerPostQueryRepository supporterRunnerPostQueryRepository; @Autowired - protected RunnerPostTagRepository runnerPostTagRepository; + protected RunnerPostTagQueryRepository runnerPostTagQueryRepository; @Autowired - protected TagRepository tagRepository; + protected TagQuerydslRepository tagQuerydslRepository; @Autowired - protected SupporterFeedbackRepository supporterFeedbackRepository; + protected SupporterFeedbackCommandRepository supporterFeedbackCommandRepository; @Autowired - protected TechnicalTagRepository technicalTagRepository; + protected TechnicalTagQueryRepository technicalTagQueryRepository; @Autowired - protected RunnerTechnicalTagRepository runnerTechnicalTagRepository; + protected RunnerTechnicalTagCommandRepository runnerTechnicalTagCommandRepository; @Autowired - protected SupporterTechnicalTagRepository supporterTechnicalTagRepository; + protected SupporterTechnicalTagCommandRepository supporterTechnicalTagCommandRepository; + + @Autowired + protected RunnerPostCommandRepository runnerPostCommandRepository; + + @Autowired + protected TagCommandRepository tagCommandRepository; + + @Autowired + protected SupporterCommandRepository supporterCommandRepository; + + @Autowired + protected SupporterRunnerPostCommandRepository supporterRunnerPostCommandRepository; + + @Autowired + protected NotificationCommandRepository notificationCommandRepository; + + @Autowired + protected NotificationQuerydslRepository notificationQuerydslRepository; + + @Autowired + protected ApplicationEventPublisher publisher; } diff --git a/backend/baton/src/test/java/touch/baton/config/converter/ConverterConfigTest.java b/backend/baton/src/test/java/touch/baton/config/converter/ConverterConfigTest.java index 5ec46c862..7af3d5bdf 100644 --- a/backend/baton/src/test/java/touch/baton/config/converter/ConverterConfigTest.java +++ b/backend/baton/src/test/java/touch/baton/config/converter/ConverterConfigTest.java @@ -12,9 +12,9 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import touch.baton.config.ArgumentResolverConfig; -import touch.baton.domain.oauth.controller.resolver.AuthMemberPrincipalArgumentResolver; -import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipalArgumentResolver; -import touch.baton.domain.oauth.controller.resolver.AuthSupporterPrincipalArgumentResolver; +import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipalArgumentResolver; +import touch.baton.domain.oauth.query.controller.resolver.AuthRunnerPrincipalArgumentResolver; +import touch.baton.domain.oauth.query.controller.resolver.AuthSupporterPrincipalArgumentResolver; import java.time.LocalDateTime; diff --git a/backend/baton/src/test/java/touch/baton/config/infra/auth/MockAuthTestConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/auth/MockBeanAuthTestConfig.java similarity index 59% rename from backend/baton/src/test/java/touch/baton/config/infra/auth/MockAuthTestConfig.java rename to backend/baton/src/test/java/touch/baton/config/infra/auth/MockBeanAuthTestConfig.java index f9a368d62..5ad553401 100644 --- a/backend/baton/src/test/java/touch/baton/config/infra/auth/MockAuthTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/infra/auth/MockBeanAuthTestConfig.java @@ -3,14 +3,14 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; -import touch.baton.config.infra.auth.jwt.MockJwtConfig; +import touch.baton.config.infra.auth.jwt.FakeJwtConfig; import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodeRequestUrlProviderCompositeConfig; -import touch.baton.config.infra.auth.oauth.client.MockOauthInformationClientCompositeConfig; +import touch.baton.config.infra.auth.oauth.client.MockAuthInformationClientCompositeConfig; @Profile("test") -@Import({MockJwtConfig.class, +@Import({FakeJwtConfig.class, MockAuthCodeRequestUrlProviderCompositeConfig.class, - MockOauthInformationClientCompositeConfig.class}) + MockAuthInformationClientCompositeConfig.class}) @TestConfiguration -public abstract class MockAuthTestConfig { +public abstract class MockBeanAuthTestConfig { } diff --git a/backend/baton/src/test/java/touch/baton/config/infra/auth/jwt/MockJwtConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/auth/jwt/FakeJwtConfig.java similarity index 84% rename from backend/baton/src/test/java/touch/baton/config/infra/auth/jwt/MockJwtConfig.java rename to backend/baton/src/test/java/touch/baton/config/infra/auth/jwt/FakeJwtConfig.java index 44eb2e03d..dfa179693 100644 --- a/backend/baton/src/test/java/touch/baton/config/infra/auth/jwt/MockJwtConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/infra/auth/jwt/FakeJwtConfig.java @@ -7,19 +7,19 @@ import touch.baton.infra.auth.jwt.JwtEncoder; @TestConfiguration -public abstract class MockJwtConfig { +public abstract class FakeJwtConfig { @Bean JwtEncoder jwtEncoder() { - return new JwtEncoder(mockJwtConfig()); + return new JwtEncoder(fakeJwtConfig()); } @Bean JwtDecoder jwtDecoder() { - return new JwtDecoder(mockJwtConfig()); + return new JwtDecoder(fakeJwtConfig()); } - private JwtConfig mockJwtConfig() { + private JwtConfig fakeJwtConfig() { return new JwtConfig("test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key", "test_issuer", 30); } diff --git a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java index 86453ceac..b1ce87bc0 100644 --- a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java @@ -2,7 +2,7 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import touch.baton.domain.oauth.token.RefreshToken; +import touch.baton.domain.oauth.command.token.RefreshToken; import touch.baton.infra.auth.jwt.JwtConfig; import touch.baton.infra.auth.jwt.JwtEncoder; diff --git a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/MockAuthCodes.java b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/FakeAuthCodes.java similarity index 91% rename from backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/MockAuthCodes.java rename to backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/FakeAuthCodes.java index 8f4d51463..83e1e9dd3 100644 --- a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/MockAuthCodes.java +++ b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/FakeAuthCodes.java @@ -1,6 +1,6 @@ package touch.baton.config.infra.auth.oauth.authcode; -public abstract class MockAuthCodes { +public abstract class FakeAuthCodes { public static String ditooAuthCode() { return "ditoo_auth_code"; diff --git a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/MockAuthCodeRequestUrlProviderCompositeConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/MockAuthCodeRequestUrlProviderCompositeConfig.java index 25a5c7bd5..a64bf0e64 100644 --- a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/MockAuthCodeRequestUrlProviderCompositeConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/authcode/MockAuthCodeRequestUrlProviderCompositeConfig.java @@ -3,8 +3,8 @@ import org.mockito.Mockito; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.authcode.AuthCodeRequestUrlProviderComposite; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.authcode.AuthCodeRequestUrlProviderComposite; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.when; diff --git a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/client/MockOauthInformationClientCompositeConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/client/MockAuthInformationClientCompositeConfig.java similarity index 66% rename from backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/client/MockOauthInformationClientCompositeConfig.java rename to backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/client/MockAuthInformationClientCompositeConfig.java index fbd78888f..2c0b655fc 100644 --- a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/client/MockOauthInformationClientCompositeConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/client/MockAuthInformationClientCompositeConfig.java @@ -3,12 +3,12 @@ import org.mockito.Mockito; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import touch.baton.config.infra.auth.oauth.authcode.MockAuthCodes; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.OauthInformation; -import touch.baton.domain.oauth.OauthType; -import touch.baton.domain.oauth.client.OauthInformationClientComposite; -import touch.baton.domain.oauth.token.SocialToken; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.OauthInformation; +import touch.baton.domain.oauth.command.OauthType; +import touch.baton.domain.oauth.command.client.OauthInformationClientComposite; +import touch.baton.domain.oauth.command.token.SocialToken; import touch.baton.fixture.domain.MemberFixture; import static org.mockito.ArgumentMatchers.any; @@ -16,28 +16,28 @@ import static org.mockito.BDDMockito.when; @TestConfiguration -public abstract class MockOauthInformationClientCompositeConfig { +public abstract class MockAuthInformationClientCompositeConfig { @Bean OauthInformationClientComposite oauthInformationClientComposite() { final OauthInformationClientComposite mock = Mockito.mock(OauthInformationClientComposite.class); - when(mock.fetchInformation(any(OauthType.class), eq(MockAuthCodes.ditooAuthCode()))) + when(mock.fetchInformation(any(OauthType.class), eq(FakeAuthCodes.ditooAuthCode()))) .thenReturn(oauthInformation(MemberFixture.createDitoo(), "ditoo_access_token")); - when(mock.fetchInformation(any(OauthType.class), eq(MockAuthCodes.ethanAuthCode()))) + when(mock.fetchInformation(any(OauthType.class), eq(FakeAuthCodes.ethanAuthCode()))) .thenReturn(oauthInformation(MemberFixture.createEthan(), "ethan_access_token")); - when(mock.fetchInformation(any(OauthType.class), eq(MockAuthCodes.hyenaAuthCode()))) + when(mock.fetchInformation(any(OauthType.class), eq(FakeAuthCodes.hyenaAuthCode()))) .thenReturn(oauthInformation(MemberFixture.createHyena(), "hyena_access_token")); - when(mock.fetchInformation(any(OauthType.class), eq(MockAuthCodes.judyAuthCode()))) + when(mock.fetchInformation(any(OauthType.class), eq(FakeAuthCodes.judyAuthCode()))) .thenReturn(oauthInformation(MemberFixture.createJudy(), "judy_access_token")); return mock; } - public OauthInformation oauthInformation(final Member member, final String mockSocialToken) { + private OauthInformation oauthInformation(final Member member, final String mockSocialToken) { return OauthInformation.builder() .oauthId(member.getOauthId()) diff --git a/backend/baton/src/test/java/touch/baton/document/github/GithubBranchApiTest.java b/backend/baton/src/test/java/touch/baton/document/github/GithubBranchApiTest.java index 339165dfd..01052060e 100644 --- a/backend/baton/src/test/java/touch/baton/document/github/GithubBranchApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/github/GithubBranchApiTest.java @@ -1,41 +1,33 @@ package touch.baton.document.github; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.controller.MemberBranchController; -import touch.baton.domain.member.service.dto.GithubBranchManageable; -import touch.baton.domain.member.service.dto.GithubRepoNameRequest; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.service.dto.GithubRepoNameRequest; import touch.baton.fixture.domain.MemberFixture; import java.util.Optional; -import static org.apache.http.HttpHeaders.*; -import static org.mockito.ArgumentMatchers.*; +import static org.apache.http.HttpHeaders.AUTHORIZATION; +import static org.apache.http.HttpHeaders.CONTENT_TYPE; +import static org.apache.http.HttpHeaders.LOCATION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(MemberBranchController.class) class GithubBranchApiTest extends RestdocsConfig { - @MockBean - private GithubBranchManageable githubBranchManageable; - - @BeforeEach - void setup() { - restdocsSetUp(new MemberBranchController(githubBranchManageable)); - } - @DisplayName("깃허브 레포 브랜치 생성 API") @Test void createMemberBranch() throws Exception { @@ -46,7 +38,7 @@ void createMemberBranch() throws Exception { final GithubRepoNameRequest request = new GithubRepoNameRequest("drunken-ditoo"); // when - when(oauthMemberRepository.findBySocialId(any())).thenReturn(Optional.ofNullable(member)); + when(oauthMemberCommandRepository.findBySocialId(any())).thenReturn(Optional.ofNullable(member)); doNothing().when(githubBranchManageable).createBranch(eq(socialId), anyString()); // then diff --git a/backend/baton/src/test/java/touch/baton/document/notification/delete/NotificationDeleteApiTest.java b/backend/baton/src/test/java/touch/baton/document/notification/delete/NotificationDeleteApiTest.java new file mode 100644 index 000000000..424ba978d --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/notification/delete/NotificationDeleteApiTest.java @@ -0,0 +1,57 @@ +package touch.baton.document.notification.delete; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.Notification; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.NotificationFixture; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.NotificationReferencedIdFixture.notificationReferencedId; + +class NotificationDeleteApiTest extends RestdocsConfig { + + @DisplayName("알림 삭제 API") + @Test + void deleteNotificationByNotificationId() throws Exception { + // given + final Member memberHyena = MemberFixture.createHyena(); + final String token = getAccessTokenBySocialId(memberHyena.getSocialId().getValue()); + final Notification notification = NotificationFixture.create(memberHyena, notificationReferencedId(1L)); + + // when + doNothing().when(notificationCommandService).deleteNotificationByMember(any(Member.class), anyLong()); + when(oauthMemberCommandRepository.findBySocialId(any())).thenReturn(Optional.ofNullable(memberHyena)); + + final Notification spyNotification = spy(notification); + when(spyNotification.getId()).thenReturn(1L); + + // then + mockMvc.perform(delete("/api/v1/notifications/{notificationId}", spyNotification.getId()) + .header(AUTHORIZATION, "Bearer " + token)) + .andExpect(status().isNoContent()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("notificationId").description("알림 식별자값") + ), + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT") + ) + )); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/notification/read/NotificationReadWithLoginedMemberApiTest.java b/backend/baton/src/test/java/touch/baton/document/notification/read/NotificationReadWithLoginedMemberApiTest.java new file mode 100644 index 000000000..5eaa9d2c0 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/notification/read/NotificationReadWithLoginedMemberApiTest.java @@ -0,0 +1,83 @@ +package touch.baton.document.notification.read; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.Notification; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.NotificationFixture; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; + +import static org.apache.http.HttpHeaders.CONTENT_TYPE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.NotificationReferencedIdFixture.notificationReferencedId; + +class NotificationReadWithLoginedMemberApiTest extends RestdocsConfig { + + @DisplayName("로그인한 사용자 알림 목록 조회 API") + @Test + void readNotificationsByMemberId() throws Exception { + // given + final Member memberHyena = MemberFixture.createHyena(); + final String token = getAccessTokenBySocialId(memberHyena.getSocialId().getValue()); + final Notification notification = NotificationFixture.create(memberHyena, notificationReferencedId(1L)); + + final Member spyMember = spy(memberHyena); + when(spyMember.getId()).thenReturn(1L); + + final Notification spyNotification = spy(notification); + when(spyNotification.getId()).thenReturn(1L); + doReturn(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES)) + .when(spyNotification) + .getCreatedAt(); + + // when + when(notificationQueryService.readNotificationsByMemberId(anyLong(), anyInt())).thenReturn(List.of(spyNotification)); + when(oauthMemberCommandRepository.findBySocialId(any())).thenReturn(Optional.of(spyMember)); + + // then + mockMvc.perform(get("/api/v1/notifications") + .header(AUTHORIZATION, "Bearer " + token) + .contentType(APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT"), + headerWithName(CONTENT_TYPE).description(APPLICATION_JSON_VALUE) + ), + responseFields( + fieldWithPath("data.[].notificationId").type(NUMBER).description("알림 식별자값(id)"), + fieldWithPath("data.[].title").type(STRING).description("알림 제목"), + fieldWithPath("data.[].message").type(STRING).description("알림 내용"), + fieldWithPath("data.[].notificationType").type(STRING).description("알림 타입 (with referencedId)"), + fieldWithPath("data.[].referencedId").type(NUMBER).description("알림 연관된 식별자값"), + fieldWithPath("data.[].isRead").type(BOOLEAN).description("알림 읽음 여부"), + fieldWithPath("data.[].createdAt").type(STRING).description("알림 생성 시간") + )) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/notification/update/NotificationUpdateApiTest.java b/backend/baton/src/test/java/touch/baton/document/notification/update/NotificationUpdateApiTest.java new file mode 100644 index 000000000..811b7f469 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/notification/update/NotificationUpdateApiTest.java @@ -0,0 +1,57 @@ +package touch.baton.document.notification.update; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.Notification; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.NotificationFixture; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.NotificationReferencedIdFixture.notificationReferencedId; + +class NotificationUpdateApiTest extends RestdocsConfig { + + @DisplayName("알림 읽기 여부 기록 API") + @Test + void updateNotificationIsReadTrueByMember() throws Exception { + // given + final Member memberHyena = MemberFixture.createHyena(); + final String token = getAccessTokenBySocialId(memberHyena.getSocialId().getValue()); + final Notification notification = NotificationFixture.create(memberHyena, notificationReferencedId(1L)); + + // when + doNothing().when(notificationCommandService).updateNotificationIsReadTrueByMember(any(Member.class), anyLong()); + when(oauthMemberCommandRepository.findBySocialId(any())).thenReturn(Optional.ofNullable(memberHyena)); + + final Notification spyNotification = spy(notification); + when(spyNotification.getId()).thenReturn(1L); + + // then + mockMvc.perform(patch("/api/v1/notifications/{notificationId}", spyNotification.getId()) + .header(AUTHORIZATION, "Bearer " + token)) + .andExpect(status().isNoContent()) + .andDo(restDocs.document( + pathParameters( + parameterWithName("notificationId").description("알림 식별자값") + ), + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT") + ) + )); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/oauth/OauthLogoutApiTest.java b/backend/baton/src/test/java/touch/baton/document/oauth/OauthLogoutApiTest.java new file mode 100644 index 000000000..a7bbb76c6 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/oauth/OauthLogoutApiTest.java @@ -0,0 +1,39 @@ +package touch.baton.document.oauth; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.Optional; + +import static org.apache.http.HttpHeaders.AUTHORIZATION; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class OauthLogoutApiTest extends RestdocsConfig { + + @DisplayName("로그아웃을 하면 리프레시 토큰이 삭제된다.") + @Test + void logout() throws Exception { + // given, when + final Member ethan = MemberFixture.createEthan(); + final String accessToken = getAccessTokenBySocialId(ethan.getSocialId().getValue()); + given(oauthMemberCommandRepository.findBySocialId(any())).willReturn(Optional.ofNullable(ethan)); + + // then + mockMvc.perform(patch("/api/v1/oauth/logout") + .header(AUTHORIZATION, "Bearer " + accessToken)) + .andExpect(status().isNoContent()) + .andDo(restDocs.document( + requestHeaders( + headerWithName("Authorization").description("Access Token") + ) + )); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java b/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java index eddd94c76..9c733933f 100644 --- a/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java @@ -1,24 +1,15 @@ package touch.baton.document.oauth.github; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.TestPropertySource; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.controller.OauthController; -import touch.baton.domain.oauth.service.OauthService; -import touch.baton.domain.oauth.token.AccessToken; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; -import touch.baton.domain.oauth.token.Tokens; -import touch.baton.infra.auth.oauth.github.GithubOauthConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.token.AccessToken; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.domain.oauth.command.token.Tokens; import java.time.LocalDateTime; @@ -35,39 +26,23 @@ import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static touch.baton.domain.oauth.OauthType.GITHUB; +import static touch.baton.domain.oauth.command.OauthType.GITHUB; -@EnableConfigurationProperties(GithubOauthConfig.class) -@TestPropertySource("classpath:application.yml") -@WebMvcTest(OauthController.class) class GithubOauthApiTest extends RestdocsConfig { - @MockBean - private OauthService oauthService; - - @Autowired - private GithubOauthConfig githubOauthConfig; - - @BeforeEach - void setUp() { - final OauthController oauthController = new OauthController(oauthService); - restdocsSetUp(oauthController); - } - @DisplayName("Github 소셜 로그인을 위한 AuthCode 를 받을 수 있도록 사용자를 redirect 한다.") @Test void github_redirect_auth_code() throws Exception { - // given & when - when(oauthService.readAuthCodeRedirect(GITHUB)) - .thenReturn(githubOauthConfig.redirectUri()); + // given, when + when(oauthCommandService.readAuthCodeRedirect(GITHUB)) + .thenReturn("https://test-redirect-url.com"); // then mockMvc.perform(get("/api/v1/oauth/{oauthType}", "github")) .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl(githubOauthConfig.redirectUri())) + .andExpect(redirectedUrl("https://test-redirect-url.com")) .andDo(restDocs.document( pathParameters( parameterWithName("oauthType").description("소셜 로그인 타입") @@ -75,8 +50,7 @@ void github_redirect_auth_code() throws Exception { responseHeaders( headerWithName(LOCATION).description("Oauth 서버 리다이렉트 URL") ) - )) - .andDo(print()); + )); } // FIXME: 2023/09/15 RFC2616 버전오류 해결해주세요. @@ -84,7 +58,7 @@ void github_redirect_auth_code() throws Exception { @DisplayName("Github 소셜 로그인을 위해 AuthCode 를 받아 SocialToken 으로 교환하여 Github 프로필 정보를 찾아오고 미가입 사용자일 경우 자동으로 회원가입을 진행하고 JWT 로 변환하여 클라이언트에게 넘겨준다.") @Test void github_login() throws Exception { - // given & when + // given, when final RefreshToken refreshToken = RefreshToken.builder() .member(mock(Member.class)) .token(new Token("mock refresh token")) @@ -92,7 +66,7 @@ void github_login() throws Exception { .build(); final Tokens tokens = new Tokens(new AccessToken("Bearer Jwt"), refreshToken); - when(oauthService.login(GITHUB, "authcode")) + when(oauthCommandService.login(GITHUB, "authcode")) .thenReturn(tokens); // then @@ -116,7 +90,6 @@ void github_login() throws Exception { cookieWithName("refreshToken").description("발급된 리프레시 토큰") ) ) - ) - .andDo(print()); + ); } } diff --git a/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java b/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java index 4495eebc5..ce0f7a6ce 100644 --- a/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java @@ -1,20 +1,15 @@ package touch.baton.document.oauth.token; import jakarta.servlet.http.Cookie; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.oauth.AuthorizationHeader; -import touch.baton.domain.oauth.controller.OauthController; -import touch.baton.domain.oauth.service.OauthService; -import touch.baton.domain.oauth.token.AccessToken; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; -import touch.baton.domain.oauth.token.Tokens; +import touch.baton.domain.oauth.command.AuthorizationHeader; +import touch.baton.domain.oauth.command.token.AccessToken; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.fixture.domain.MemberFixture; import java.time.Duration; @@ -30,25 +25,14 @@ import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(OauthController.class) class RefreshTokenApiTest extends RestdocsConfig { - @MockBean - private OauthService oauthService; - - @BeforeEach - void setUp() { - final OauthController oauthController = new OauthController(oauthService); - restdocsSetUp(oauthController); - } - @DisplayName("만료된 jwt 토큰과 refresh token 으로 refresh 요청을 하면 새로운 토큰들이 반환된다.") @Test void refresh() throws Exception { - // given & when + // given, when final RefreshToken refreshToken = RefreshToken.builder() .token(new Token("refresh-token")) .member(MemberFixture.createEthan()) @@ -57,7 +41,7 @@ void refresh() throws Exception { final Tokens tokens = new Tokens(new AccessToken("renew access token"), refreshToken); final Cookie cookie = createCookie(); - given(oauthService.reissueAccessToken(any(AuthorizationHeader.class), any(String.class))).willReturn(tokens); + given(oauthCommandService.reissueAccessToken(any(AuthorizationHeader.class), any(String.class))).willReturn(tokens); // then mockMvc.perform(post("/api/v1/oauth/refresh") @@ -77,8 +61,7 @@ void refresh() throws Exception { responseCookies( cookieWithName("refreshToken").description("새로 발급된 리프레시 토큰") ) - )) - .andDo(print()); + )); } private Cookie createCookie() { diff --git a/backend/baton/src/test/java/touch/baton/document/profile/member/read/MemberReadWithLoginedMemberApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/member/read/MemberReadWithLoginedMemberApiTest.java index 849b6b086..08bd7bc34 100644 --- a/backend/baton/src/test/java/touch/baton/document/profile/member/read/MemberReadWithLoginedMemberApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/profile/member/read/MemberReadWithLoginedMemberApiTest.java @@ -1,12 +1,9 @@ package touch.baton.document.profile.member.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.controller.MemberProfileController; +import touch.baton.domain.member.command.Member; import touch.baton.fixture.domain.MemberFixture; import java.util.Optional; @@ -29,13 +26,7 @@ import static touch.baton.fixture.vo.OauthIdFixture.oauthId; import static touch.baton.fixture.vo.SocialIdFixture.socialId; -@WebMvcTest(MemberProfileController.class) -public class MemberReadWithLoginedMemberApiTest extends RestdocsConfig { - - @BeforeEach - void setUp() { - restdocsSetUp(new MemberProfileController()); - } +class MemberReadWithLoginedMemberApiTest extends RestdocsConfig { @DisplayName("로그인 한 맴버 정보 조회 API") @Test @@ -53,7 +44,7 @@ void readLoginMemberByAccessToken() throws Exception { final String token = getAccessTokenBySocialId(socialId); // when - when(oauthMemberRepository.findBySocialId(any())).thenReturn(Optional.ofNullable(member)); + when(oauthMemberCommandRepository.findBySocialId(any())).thenReturn(Optional.ofNullable(member)); // then mockMvc.perform(get("/api/v1/profile/me").header(AUTHORIZATION, "Bearer " + token)) @@ -64,7 +55,6 @@ void readLoginMemberByAccessToken() throws Exception { fieldWithPath("name").type(STRING).description("사용자 이름"), fieldWithPath("imageUrl").type(STRING).description("사용자 프로필 이미지 url") ) - )) - .andDo(print()); + )); } } diff --git a/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadByGuestApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadByGuestApiTest.java index 7ed538a3a..92ef066ce 100644 --- a/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadByGuestApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadByGuestApiTest.java @@ -1,17 +1,11 @@ package touch.baton.document.profile.runner.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.controller.RunnerProfileController; -import touch.baton.domain.runner.service.RunnerService; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.TechnicalTagFixture; @@ -33,25 +27,12 @@ import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerProfileController.class) class RunnerReadByGuestApiTest extends RestdocsConfig { - @MockBean - private RunnerPostService runnerPostService; - - @MockBean - private RunnerService runnerService; - - @BeforeEach - void setUp() { - restdocsSetUp(new RunnerProfileController(runnerPostService, runnerService)); - } - @DisplayName("러너 본인 프로필 조회 API") @Test void readMyProfileByToken() throws Exception { @@ -61,12 +42,11 @@ void readMyProfileByToken() throws Exception { final Runner runner = RunnerFixture.createRunner(MemberFixture.createHyena(), List.of(java, spring)); final String token = getAccessTokenBySocialId(runner.getMember().getSocialId().getValue()); - when(oauthRunnerRepository.joinByMemberSocialId(notNull())).thenReturn(Optional.ofNullable(runner)); + when(oauthRunnerCommandRepository.joinByMemberSocialId(notNull())).thenReturn(Optional.ofNullable(runner)); // then mockMvc.perform(get("/api/v1/profile/runner/me") .header(AUTHORIZATION, "Bearer " + token)) - .andDo(print()) .andDo(restDocs.document( requestHeaders( headerWithName(AUTHORIZATION).description("Bearer JWT") @@ -79,8 +59,7 @@ void readMyProfileByToken() throws Exception { fieldWithPath("introduction").type(STRING).description("러너 자기소개"), fieldWithPath("technicalTags").type(ARRAY).description("러너 기술 태그 목록") ) - )) - .andDo(print()); + )); } @DisplayName("러너 프로필 상세 조회 API") @@ -95,7 +74,7 @@ void readRunnerProfile() throws Exception { // when when(spyRunner.getId()).thenReturn(1L); - when(runnerService.readByRunnerId(anyLong())).thenReturn(spyRunner); + when(runnerQueryService.readByRunnerId(anyLong())).thenReturn(spyRunner); // then mockMvc.perform(get("/api/v1/profile/runner/{runnerId}", 1L)) diff --git a/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadByRunnerIdApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadSimpleByRunnerIdApiTest.java similarity index 75% rename from backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadByRunnerIdApiTest.java rename to backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadSimpleByRunnerIdApiTest.java index 875d291fc..536312c37 100644 --- a/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadByRunnerIdApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadSimpleByRunnerIdApiTest.java @@ -1,17 +1,11 @@ package touch.baton.document.profile.runner.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.controller.RunnerProfileController; -import touch.baton.domain.runner.service.RunnerService; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.TechnicalTagFixture; @@ -33,20 +27,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(RunnerProfileController.class) -public class RunnerReadByRunnerIdApiTest extends RestdocsConfig { - - @MockBean - private RunnerPostService runnerPostService; - - @MockBean - private RunnerService runnerService; - - @BeforeEach - void setUp() { - final RunnerProfileController runnerProfileController = new RunnerProfileController(runnerPostService, runnerService); - restdocsSetUp(runnerProfileController); - } +class RunnerReadSimpleByRunnerIdApiTest extends RestdocsConfig { @DisplayName("러너 프로필 상세 조회 API") @Test @@ -60,7 +41,7 @@ void readRunnerProfile() throws Exception { // when when(spyRunner.getId()).thenReturn(1L); - when(runnerService.readByRunnerId(anyLong())).thenReturn(spyRunner); + when(runnerQueryService.readByRunnerId(anyLong())).thenReturn(spyRunner); // then mockMvc.perform(get("/api/v1/profile/runner/{runnerId}", 1L)) diff --git a/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadWithLoginedRunnerApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadWithLoginedRunnerApiTest.java index 71e4997e7..251e0f4d2 100644 --- a/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadWithLoginedRunnerApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/profile/runner/read/RunnerReadWithLoginedRunnerApiTest.java @@ -1,16 +1,10 @@ package touch.baton.document.profile.runner.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.controller.RunnerProfileController; -import touch.baton.domain.runner.service.RunnerService; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.TechnicalTagFixture; @@ -28,24 +22,10 @@ import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerProfileController.class) class RunnerReadWithLoginedRunnerApiTest extends RestdocsConfig { - @MockBean - private RunnerPostService runnerPostService; - - @MockBean - private RunnerService runnerService; - - @BeforeEach - void setUp() { - final RunnerProfileController runnerProfileController = new RunnerProfileController(runnerPostService, runnerService); - restdocsSetUp(runnerProfileController); - } - @DisplayName("러너 본인 프로필 조회 API") @Test void readMyProfileByToken() throws Exception { @@ -55,12 +35,11 @@ void readMyProfileByToken() throws Exception { final Runner runner = RunnerFixture.createRunner(MemberFixture.createHyena(), List.of(java, spring)); final String token = getAccessTokenBySocialId(runner.getMember().getSocialId().getValue()); - when(oauthRunnerRepository.joinByMemberSocialId(notNull())).thenReturn(Optional.ofNullable(runner)); + when(oauthRunnerCommandRepository.joinByMemberSocialId(notNull())).thenReturn(Optional.ofNullable(runner)); // then mockMvc.perform(get("/api/v1/profile/runner/me") .header(AUTHORIZATION, "Bearer " + token)) - .andDo(print()) .andDo(restDocs.document( requestHeaders( headerWithName(AUTHORIZATION).description("Bearer JWT") diff --git a/backend/baton/src/test/java/touch/baton/document/profile/runner/update/RunnerUpdateApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/runner/update/RunnerUpdateApiTest.java index bea871752..6d5503a59 100644 --- a/backend/baton/src/test/java/touch/baton/document/profile/runner/update/RunnerUpdateApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/profile/runner/update/RunnerUpdateApiTest.java @@ -1,18 +1,12 @@ package touch.baton.document.profile.runner.update; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.controller.RunnerProfileController; -import touch.baton.domain.runner.service.RunnerService; -import touch.baton.domain.runner.service.dto.RunnerUpdateRequest; -import touch.baton.domain.runnerpost.service.RunnerPostService; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.service.dto.RunnerUpdateRequest; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; @@ -32,24 +26,10 @@ import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(RunnerProfileController.class) -public class RunnerUpdateApiTest extends RestdocsConfig { - - @MockBean - private RunnerService runnerService; - - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerProfileController runnerProfileController = new RunnerProfileController(runnerPostService, runnerService); - restdocsSetUp(runnerProfileController); - } +class RunnerUpdateApiTest extends RestdocsConfig { @DisplayName("러너 프로필 수정 API") @Test @@ -63,7 +43,7 @@ void updateRunnerProfile() throws Exception { final String token = getAccessTokenBySocialId(socialId); // when - when(oauthRunnerRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(judyRunner)); + when(oauthRunnerCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(judyRunner)); // then mockMvc.perform(patch("/api/v1/profile/runner/me") @@ -84,7 +64,6 @@ void updateRunnerProfile() throws Exception { fieldWithPath("technicalTags.[]").type(ARRAY).description("변경할 기술 태그 목록") ), responseHeaders(headerWithName(LOCATION).description("redirect uri")) - )) - .andDo(print()); + )); } } diff --git a/backend/baton/src/test/java/touch/baton/document/profile/supporter/read/SupporterReadByGuestApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/supporter/read/SupporterReadByGuestApiTest.java index 2a8b5ff88..7ba80f017 100644 --- a/backend/baton/src/test/java/touch/baton/document/profile/supporter/read/SupporterReadByGuestApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/profile/supporter/read/SupporterReadByGuestApiTest.java @@ -1,17 +1,12 @@ package touch.baton.document.profile.supporter.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.controller.SupporterProfileController; -import touch.baton.domain.supporter.service.SupporterService; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.TechnicalTagFixture; @@ -38,18 +33,8 @@ import static touch.baton.fixture.vo.ReviewCountFixture.reviewCount; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(SupporterProfileController.class) class SupporterReadByGuestApiTest extends RestdocsConfig { - @MockBean - private SupporterService supporterService; - - @BeforeEach - void setUp() { - final SupporterProfileController supporterProfileController = new SupporterProfileController(supporterService); - restdocsSetUp(supporterProfileController); - } - @DisplayName("서포터 프로필 조회 API") @Test void readProfileBySupporterId() throws Exception { @@ -60,7 +45,7 @@ void readProfileBySupporterId() throws Exception { final Supporter spySupporter = spy(supporter); when(spySupporter.getId()).thenReturn(1L); - when(supporterService.readBySupporterId(spySupporter.getId())).thenReturn(spySupporter); + when(supporterQueryService.readBySupporterId(spySupporter.getId())).thenReturn(spySupporter); // then mockMvc.perform(get("/api/v1/profile/supporter/{supporterId}", 1L)) @@ -78,8 +63,7 @@ void readProfileBySupporterId() throws Exception { fieldWithPath("introduction").type(STRING).description("서포터 자기소개"), fieldWithPath("technicalTags").type(ARRAY).description("서포터 기술 태그 목록") ) - )) - .andDo(print()); + )); } @DisplayName("서포터 마이페이지 프로필 조회 API") @@ -96,7 +80,7 @@ void readMyProfileByToken() throws Exception { // when when(spySupporter.getId()).thenReturn(1L); - when(oauthSupporterRepository.joinByMemberSocialId(any(SocialId.class))).thenReturn(Optional.ofNullable(spySupporter)); + when(oauthSupporterCommandRepository.joinByMemberSocialId(any(SocialId.class))).thenReturn(Optional.ofNullable(spySupporter)); // then mockMvc.perform(get("/api/v1/profile/supporter/me").header(AUTHORIZATION, "Bearer " + accessToken)) diff --git a/backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterUpdateApiTest.java b/backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterUpdateApiTest.java index 09517a8d9..17949cb09 100644 --- a/backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterUpdateApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/profile/supporter/update/SupporterUpdateApiTest.java @@ -1,17 +1,12 @@ package touch.baton.document.profile.supporter.update; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.controller.SupporterProfileController; -import touch.baton.domain.supporter.service.SupporterService; -import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.service.dto.SupporterUpdateRequest; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.SupporterFixture; @@ -41,17 +36,7 @@ import static touch.baton.fixture.vo.OauthIdFixture.oauthId; import static touch.baton.fixture.vo.SocialIdFixture.socialId; -@WebMvcTest(SupporterProfileController.class) -public class SupporterUpdateApiTest extends RestdocsConfig { - - @MockBean - private SupporterService supporterService; - - @BeforeEach - void setUp() { - final SupporterProfileController supporterProfileController = new SupporterProfileController(supporterService); - restdocsSetUp(supporterProfileController); - } +class SupporterUpdateApiTest extends RestdocsConfig { @DisplayName("서포터 프로필 수정 API") @Test @@ -72,7 +57,7 @@ void updateSupporterProfile() throws Exception { final String token = getAccessTokenBySocialId(socialId); // when - when(oauthSupporterRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporter)); + when(oauthSupporterCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporter)); // then mockMvc.perform(patch("/api/v1/profile/supporter/me") @@ -93,7 +78,6 @@ void updateSupporterProfile() throws Exception { fieldWithPath("technicalTags.[]").type(ARRAY).description("변경할 기술 태그 목록") ), responseHeaders(headerWithName(LOCATION).description("redirect uri")) - )) - .andDo(print()); + )); } } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostApplicantApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostApplicantApiTest.java index 9d00fb42a..6986a0e26 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostApplicantApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostApplicantApiTest.java @@ -1,19 +1,14 @@ package touch.baton.document.runnerpost.create; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.service.dto.RunnerPostApplicantCreateRequest; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostApplicantCreateRequest; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.tag.command.Tag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -27,33 +22,26 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.springframework.http.HttpHeaders.*; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.HttpHeaders.LOCATION; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static touch.baton.fixture.vo.DeadlineFixture.deadline; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerPostController.class) class RunnerPostApplicantApiTest extends RestdocsConfig { - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } - @DisplayName("Supporter 가 RunnerPost 에 리뷰를 제안한다.") @Test void createRunnerPostApplicant() throws Exception { @@ -68,9 +56,9 @@ void createRunnerPostApplicant() throws Exception { // when final RunnerPost spyRunnerPost = spy(runnerPost); when(spyRunnerPost.getId()).thenReturn(1L); - when(runnerPostService.createRunnerPostApplicant(any(), any(), any())).thenReturn(1L); + when(runnerPostCommandService.createRunnerPostApplicant(any(), any(), any())).thenReturn(1L); - when(oauthSupporterRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporterHyena)); + when(oauthSupporterCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporterHyena)); final String token = getAccessTokenBySocialId(supporterHyena.getMember().getSocialId().getValue()); @@ -97,7 +85,6 @@ void createRunnerPostApplicant() throws Exception { responseHeaders( headerWithName(LOCATION).description("redirect uri") ) - )) - .andDo(print()); + )); } } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java index f42f82783..ef036474b 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/create/RunnerPostCreateApiTest.java @@ -1,16 +1,11 @@ package touch.baton.document.runnerpost.create; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; @@ -18,27 +13,22 @@ import java.util.List; import java.util.Optional; -import static org.apache.http.HttpHeaders.*; +import static org.apache.http.HttpHeaders.AUTHORIZATION; +import static org.apache.http.HttpHeaders.CONTENT_TYPE; +import static org.apache.http.HttpHeaders.LOCATION; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(RunnerPostController.class) class RunnerPostCreateApiTest extends RestdocsConfig { - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - restdocsSetUp(new RunnerPostController(runnerPostService)); - } - @DisplayName("러너 게시글 등록 API") @Test void createRunnerPost() throws Exception { @@ -58,8 +48,8 @@ void createRunnerPost() throws Exception { ); // when - when(runnerPostService.createRunnerPost(any(Runner.class), any(RunnerPostCreateRequest.class))).thenReturn(1L); - when(oauthRunnerRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(runner)); + when(runnerPostCommandService.createRunnerPost(any(Runner.class), any(RunnerPostCreateRequest.class))).thenReturn(1L); + when(oauthRunnerCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(runner)); // then mockMvc.perform(post("/api/v1/posts/runner") diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/delete/RunnerPostDeleteApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/delete/RunnerPostDeleteApiTest.java index c2d34ed11..0df5cd56a 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/delete/RunnerPostDeleteApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/delete/RunnerPostDeleteApiTest.java @@ -1,15 +1,10 @@ package touch.baton.document.runnerpost.delete; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -29,17 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static touch.baton.fixture.vo.DeadlineFixture.deadline; -@WebMvcTest(RunnerPostController.class) -public class RunnerPostDeleteApiTest extends RestdocsConfig { - - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } +class RunnerPostDeleteApiTest extends RestdocsConfig { @DisplayName("러너 게시글 삭제 API") @Test @@ -52,7 +37,7 @@ void deleteByRunnerPostId() throws Exception { final RunnerPost spyRunnerPost = spy(runnerPost); // when - when(oauthRunnerRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(runner)); + when(oauthRunnerCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(runner)); when(spyRunnerPost.getId()).thenReturn(1L); // then diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountOfSupporterByGuestApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountOfSupporterByGuestApiTest.java new file mode 100644 index 000000000..641c06d1f --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountOfSupporterByGuestApiTest.java @@ -0,0 +1,72 @@ +package touch.baton.document.runnerpost.read; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.SupporterFixture; +import touch.baton.fixture.domain.SupporterRunnerPostFixture; + +import java.time.LocalDateTime; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; + +class RunnerPostCountOfSupporterByGuestApiTest extends RestdocsConfig { + + @DisplayName("서포터가 완료한 러너 게시글 개수 조회 API") + @Test + void countRunnerPostBySupporterIdAndReviewStatus() throws Exception { + // given + final Runner runner = RunnerFixture.createRunner(MemberFixture.createHyena()); + final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline); + + final Supporter supporter = SupporterFixture.create(MemberFixture.createDitoo()); + SupporterRunnerPostFixture.create(runnerPost, supporter); + runnerPost.assignSupporter(supporter); + + final Supporter spySupporter = spy(supporter); + given(spySupporter.getId()).willReturn(1L); + + // when + final long expectedCount = 1L; + when(runnerPostQueryService.countRunnerPostBySupporterIdAndReviewStatus(eq(1L), eq(ReviewStatus.DONE))) + .thenReturn(expectedCount); + + // then + mockMvc.perform(get("/api/v1/posts/runner/search/count") + .queryParam( + "supporterId", String.valueOf(spySupporter.getId()) + )) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andDo(restDocs.document( + queryParameters( + parameterWithName("supporterId").description("서포터 식별자 값") + ), + responseFields( + fieldWithPath("count").type(NUMBER).optional().description("게시글 개수") + )) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedRunnerApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedRunnerApiTest.java new file mode 100644 index 000000000..6782990c3 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedRunnerApiTest.java @@ -0,0 +1,80 @@ +package touch.baton.document.runnerpost.read; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.tag.command.Tag; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.TagFixture; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; +import static touch.baton.fixture.vo.TagNameFixture.tagName; + +class RunnerPostCountWithLoginedRunnerApiTest extends RestdocsConfig { + + @DisplayName("로그인한 러너와 관련된 러너 게시글 개수 조회 API") + @Test + void countRunnerPostByLoginedRunnerAndReviewStatus() throws Exception { + // given + final String socialId = "ditooSocialId"; + final Member loginedMember = MemberFixture.createWithSocialId(socialId); + final Runner loginedRunner = RunnerFixture.createRunner(loginedMember); + final String token = getAccessTokenBySocialId(socialId); + + final Tag javaTag = TagFixture.create(tagName("자바")); + final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); + RunnerPostFixture.create(loginedRunner, deadline, List.of(javaTag)); + final Runner spyLoginedRunner = spy(loginedRunner); + given(oauthRunnerCommandRepository.joinByMemberSocialId(any())).willReturn(Optional.ofNullable(spyLoginedRunner)); + + // when + final long expectedCount = 1L; + when(runnerPostQueryService.countRunnerPostByRunnerIdAndReviewStatus(eq(1L), eq(ReviewStatus.NOT_STARTED))) + .thenReturn(expectedCount); + + // then + mockMvc.perform(get("/api/v1/posts/runner/me/runner/count") + .header(AUTHORIZATION, "Bearer " + token) + .queryParam("reviewStatus", ReviewStatus.NOT_STARTED.name())) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT") + ), + queryParameters( + parameterWithName("reviewStatus").description("리뷰 상태") + ), + responseFields( + fieldWithPath("count").type(NUMBER).optional().description("게시글 개수") + )) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedSupporterApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedSupporterApiTest.java new file mode 100644 index 000000000..f607b1508 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostCountWithLoginedSupporterApiTest.java @@ -0,0 +1,84 @@ +package touch.baton.document.runnerpost.read; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.SupporterFixture; +import touch.baton.fixture.domain.SupporterRunnerPostFixture; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; + +class RunnerPostCountWithLoginedSupporterApiTest extends RestdocsConfig { + + @DisplayName("로그인한 서포터와 관련된 러너 게시글 개수 조회 API") + @Test + void countRunnerPostByLoginedSupporterAndReviewStatus() throws Exception { + // given + final String socialId = "ditooSocialId"; + final Member loginedMember = MemberFixture.createWithSocialId(socialId); + final Supporter loginedSupporter = SupporterFixture.create(loginedMember); + final String token = getAccessTokenBySocialId(socialId); + + final Runner runner = RunnerFixture.createRunner(MemberFixture.createHyena()); + + final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline); + SupporterRunnerPostFixture.create(runnerPost, loginedSupporter); + runnerPost.assignSupporter(loginedSupporter); + + final Supporter spyLoginedSupporter = spy(loginedSupporter); + given(oauthSupporterCommandRepository.joinByMemberSocialId(any())).willReturn(Optional.ofNullable(spyLoginedSupporter)); + + // when + final long expectedCount = 1L; + when(runnerPostQueryService.countRunnerPostBySupporterIdAndReviewStatus(eq(1L), eq(ReviewStatus.NOT_STARTED))) + .thenReturn(expectedCount); + + // then + mockMvc.perform(get("/api/v1/posts/runner/me/supporter/count") + .header(AUTHORIZATION, "Bearer " + token) + .queryParam("reviewStatus", ReviewStatus.NOT_STARTED.name())) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT") + ), + queryParameters( + parameterWithName("reviewStatus").description("리뷰 상태") + ), + responseFields( + fieldWithPath("count").type(NUMBER).optional().description("게시글 개수") + )) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java deleted file mode 100644 index 83897c640..000000000 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadAllApiTest.java +++ /dev/null @@ -1,183 +0,0 @@ -package touch.baton.document.runnerpost.read; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import touch.baton.config.RestdocsConfig; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.tag.Tag; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.TagFixture; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.when; -import static org.mockito.Mockito.spy; -import static org.springframework.http.HttpHeaders.AUTHORIZATION; -import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; -import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; -import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; -import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; -import static org.springframework.restdocs.payload.JsonFieldType.STRING; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; -import static touch.baton.fixture.vo.TagNameFixture.tagName; - -@WebMvcTest(RunnerPostController.class) -class RunnerPostReadAllApiTest extends RestdocsConfig { - - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } - - @DisplayName("러너 게시글 전체 조회 API") - @Test - void readRunnerPostsByReviewStatus() throws Exception { - // given - final Runner runner = RunnerFixture.createRunner(MemberFixture.createHyena()); - final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); - final Tag javaTag = TagFixture.create(tagName("자바")); - final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline, List.of(javaTag)); - final RunnerPost spyRunnerPost = spy(runnerPost); - final ReviewStatus reviewStatus = ReviewStatus.IN_PROGRESS; - given(spyRunnerPost.getId()).willReturn(1L); - - // when - final List runnerPosts = List.of(spyRunnerPost); - final PageRequest pageOne = PageRequest.of(1, 10); - final PageImpl pageRunnerPosts = new PageImpl<>(runnerPosts, pageOne, runnerPosts.size()); - when(spyRunnerPost.getReviewStatus()).thenReturn(reviewStatus); - when(runnerPostService.readRunnerPostsByReviewStatus(any(), any())).thenReturn(pageRunnerPosts); - when(runnerPostService.readCountsByRunnerPostIds(anyList())).thenReturn(List.of(1L)); - - // then - mockMvc.perform(get("/api/v1/posts/runner") - .queryParam("size", String.valueOf(pageOne.getPageSize())) - .queryParam("page", String.valueOf(pageOne.getPageNumber())) - .queryParam("reviewStatus", String.valueOf(reviewStatus))) - .andExpect(status().isOk()) - .andExpect(content().contentType(APPLICATION_JSON)) - .andDo(restDocs.document( - queryParameters( - parameterWithName("size").description("페이지 사이즈"), - parameterWithName("page").description("페이지 번호"), - parameterWithName("reviewStatus").description("리뷰 상태") - ), - responseFields( - fieldWithPath("data.[].runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), - fieldWithPath("data.[].title").type(STRING).description("러너 게시글의 제목"), - fieldWithPath("data.[].deadline").type(STRING).description("러너 게시글의 마감 기한"), - fieldWithPath("data.[].watchedCount").type(NUMBER).description("러너 게시글의 조회수"), - fieldWithPath("data.[].reviewStatus").type(STRING).description("러너 게시글의 리뷰 상태"), - fieldWithPath("data.[].runnerProfile.name").type(STRING).description("러너 게시글의 러너 프로필 이름"), - fieldWithPath("data.[].runnerProfile.imageUrl").type(STRING).description("러너 게시글의 러너 프로필 이미지"), - fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), - fieldWithPath("data.[].applicantCount").type(NUMBER).description("러너 게시글에 신청한 서포터 수"), - fieldWithPath("pageInfo.isFirst").type(BOOLEAN).description("첫 번째 페이지인지"), - fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지인지"), - fieldWithPath("pageInfo.hasNext").type(BOOLEAN).description("다음 페이지가 있는지"), - fieldWithPath("pageInfo.totalPages").type(NUMBER).description("총 페이지 수"), - fieldWithPath("pageInfo.totalElements").type(NUMBER).description("총 데이터 수"), - fieldWithPath("pageInfo.currentPage").type(NUMBER).description("현재 페이지"), - fieldWithPath("pageInfo.currentSize").type(NUMBER).description("현재 페이지 데이터 수") - )) - ); - } - - @DisplayName("러너와 연관된 러너 게시글 페이징 조회 API") - @Test - void readRunnerMyPage() throws Exception { - // given - final Runner runnerJudy = RunnerFixture.createRunner(MemberFixture.createJudy()); - final String token = getAccessTokenBySocialId(runnerJudy.getMember().getSocialId().getValue()); - - when(oauthRunnerRepository.joinByMemberSocialId(notNull())) - .thenReturn(Optional.ofNullable(runnerJudy)); - - final Tag javaTag = TagFixture.create(tagName("자바")); - final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); - final RunnerPost runnerPost = RunnerPostFixture.create(runnerJudy, deadline, List.of(javaTag)); - - // when - final RunnerPost spyRunnerPost = spy(runnerPost); - when(spyRunnerPost.getId()).thenReturn(1L); - - final List runnerPosts = List.of(spyRunnerPost); - final PageRequest pageOne = PageRequest.of(1, 10); - final PageImpl pageRunnerPosts = new PageImpl<>(runnerPosts, pageOne, runnerPosts.size()); - when(runnerPostService.readRunnerPostsByRunnerIdAndReviewStatus(any(), any(), any())) - .thenReturn(pageRunnerPosts); - when(runnerPostService.readCountsByRunnerPostIds(anyList())) - .thenReturn(List.of(0L)); - - // then - mockMvc.perform(get("/api/v1/posts/runner/me/runner") - .header(AUTHORIZATION, "Bearer " + token) - .characterEncoding(UTF_8) - .accept(APPLICATION_JSON) - .queryParam("size", String.valueOf(pageOne.getPageSize())) - .queryParam("page", String.valueOf(pageOne.getPageNumber())) - .queryParam("reviewStatus", ReviewStatus.IN_PROGRESS.name())) - .andExpect(status().isOk()) - .andExpect(content().contentType(APPLICATION_JSON)) - .andDo(restDocs.document( - requestHeaders( - headerWithName(AUTHORIZATION).description("Bearer JWT") - ), - queryParameters( - parameterWithName("size").description("페이지 사이즈"), - parameterWithName("page").description("페이지 번호"), - parameterWithName("reviewStatus").description("리뷰 상태") - ), - responseFields( - fieldWithPath("data.[].runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), - fieldWithPath("data.[].supporterId").type(NUMBER).optional().description("서포터 식별자값(id)은 null 일 수 있다"), - fieldWithPath("data.[].title").type(STRING).description("러너 게시글 제목"), - fieldWithPath("data.[].deadline").type(STRING).description("러너 게시글의 마감 기한"), - fieldWithPath("data.[].tags").type(ARRAY).description("러너 게시글 태그 목록"), - fieldWithPath("data.[].watchedCount").type(NUMBER).description("러너 게시글의 조회수"), - fieldWithPath("data.[].applicantCount").type(NUMBER).description("러너 게시글에 신청한 서포터 수"), - fieldWithPath("data.[].reviewStatus").type(STRING).description("러너 게시글 리뷰 상태"), - fieldWithPath("data.[].isReviewed").type(BOOLEAN).description("러너 게시글을 리뷰해준 서포터에 대한 피드백 유무"), - fieldWithPath("pageInfo.isFirst").type(BOOLEAN).description("첫 번째 페이지인지"), - fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 인지"), - fieldWithPath("pageInfo.hasNext").type(BOOLEAN).description("다음 페이지가 있는지"), - fieldWithPath("pageInfo.totalPages").type(NUMBER).description("총 페이지 수"), - fieldWithPath("pageInfo.totalElements").type(NUMBER).description("총 데이터 수"), - fieldWithPath("pageInfo.currentPage").type(NUMBER).description("현재 페이지 번호"), - fieldWithPath("pageInfo.currentSize").type(NUMBER).description("현재 페이지 데이터 수") - )) - ); - } -} diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java index 6067176c0..bdc9f55aa 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java @@ -1,119 +1,103 @@ package touch.baton.document.runnerpost.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.common.request.PageParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; +import touch.baton.domain.tag.command.Tag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.RunnerPostTagFixture; import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.TagFixture; import java.time.LocalDateTime; import java.util.List; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static touch.baton.fixture.domain.TechnicalTagFixture.createJava; -import static touch.baton.fixture.domain.TechnicalTagFixture.createSpring; import static touch.baton.fixture.vo.DeadlineFixture.deadline; -import static touch.baton.fixture.vo.ReviewCountFixture.reviewCount; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerPostController.class) class RunnerPostReadOfSupporterByGuestApiTest extends RestdocsConfig { - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } - @DisplayName("서포터와 연관된 러너 게시글 페이징 조회 API") @Test - void readReferencedBySupporter() throws Exception { + void readRunnerPostBySupporterIdAndReviewStatus() throws Exception { // given - final Runner runnerJudy = RunnerFixture.createRunner(MemberFixture.createJudy()); - final Supporter supporterHyena = SupporterFixture.create(reviewCount(10), MemberFixture.createHyena(), List.of(createJava(), createSpring())); + final Runner runner = RunnerFixture.createRunner(MemberFixture.createDitoo()); + final Supporter supporter = SupporterFixture.create(MemberFixture.createHyena()); final Tag javaTag = TagFixture.create(tagName("자바")); final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); - final RunnerPost runnerPost = RunnerPostFixture.create(runnerJudy, deadline, List.of(javaTag)); - runnerPost.assignSupporter(supporterHyena); + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline, List.of(javaTag)); + runnerPost.assignSupporter(supporter); - // when + final Supporter spySupporter = spy(supporter); + given(spySupporter.getId()).willReturn(1L); final RunnerPost spyRunnerPost = spy(runnerPost); - final Supporter spySupporterHyena = spy(supporterHyena); - when(spySupporterHyena.getId()).thenReturn(1L); - when(spyRunnerPost.getId()).thenReturn(1L); + given(spyRunnerPost.getId()).willReturn(1L); - final List runnerPosts = List.of(spyRunnerPost); - final PageRequest pageOne = PageRequest.of(1, 10); - final PageImpl pageRunnerPosts = new PageImpl<>(runnerPosts, pageOne, runnerPosts.size()); - when(runnerPostService.readRunnerPostsBySupporterIdAndReviewStatus(any(), any(), any())) - .thenReturn(pageRunnerPosts); - when(runnerPostService.readCountsByRunnerPostIds(anyList())).thenReturn(List.of(1L)); + // when + final List responses = List.of(RunnerPostResponse.Simple.of( + spyRunnerPost, + 0L, + List.of(RunnerPostTagFixture.create(spyRunnerPost, javaTag)) + )); + final PageResponse pageResponse = PageResponse.of(responses, new PageParams(2L, 10)); + when(runnerPostQueryService.pageRunnerPostBySupporterIdAndReviewStatus(any(PageParams.class), anyLong(), any(ReviewStatus.class))) + .thenReturn(pageResponse); // then mockMvc.perform(get("/api/v1/posts/runner/search") - .characterEncoding(UTF_8) - .accept(APPLICATION_JSON) - .queryParam("size", String.valueOf(pageOne.getPageSize())) - .queryParam("page", String.valueOf(pageOne.getPageNumber())) - .queryParam("supporterId", String.valueOf(spySupporterHyena.getId())) + .queryParam("cursor", String.valueOf(1000L)) + .queryParam("limit", String.valueOf(10)) + .queryParam("supporterId", String.valueOf(spySupporter.getId())) .queryParam("reviewStatus", ReviewStatus.IN_PROGRESS.name())) .andExpect(status().isOk()) .andExpect(content().contentType(APPLICATION_JSON)) .andDo(restDocs.document( queryParameters( - parameterWithName("size").description("페이지 사이즈"), - parameterWithName("page").description("페이지 번호"), + parameterWithName("cursor").description("(Optional) 이전 페이지 마지막 게시글 식별자값(id)"), + parameterWithName("limit").description("페이지 사이즈"), parameterWithName("supporterId").description("서포터 식별자값"), - parameterWithName("reviewStatus").description("리뷰 상태") + parameterWithName("reviewStatus").description("(Optional) 리뷰 상태") ), responseFields( - fieldWithPath("pageInfo.isFirst").type(BOOLEAN).description("첫 번째 페이지인지"), - fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 인지"), - fieldWithPath("pageInfo.hasNext").type(BOOLEAN).description("다음 페이지가 있는지"), - fieldWithPath("pageInfo.totalPages").type(NUMBER).description("총 페이지 수"), - fieldWithPath("pageInfo.totalElements").type(NUMBER).description("총 데이터 수"), - fieldWithPath("pageInfo.currentPage").type(NUMBER).description("현재 페이지 번호"), - fieldWithPath("pageInfo.currentSize").type(NUMBER).description("현재 페이지 데이터 수"), fieldWithPath("data.[].runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), - fieldWithPath("data.[].title").type(STRING).description("러너 게시글 제목"), + fieldWithPath("data.[].title").type(STRING).description("러너 게시글의 제목"), fieldWithPath("data.[].deadline").type(STRING).description("러너 게시글의 마감 기한"), - fieldWithPath("data.[].tags").type(ARRAY).description("러너 게시글 태그 목록"), fieldWithPath("data.[].watchedCount").type(NUMBER).description("러너 게시글의 조회수"), fieldWithPath("data.[].applicantCount").type(NUMBER).description("러너 게시글에 신청한 서포터 수"), - fieldWithPath("data.[].reviewStatus").type(STRING).description("러너 게시글 리뷰 상태") + fieldWithPath("data.[].reviewStatus").type(STRING).description("러너 게시글의 리뷰 상태"), + fieldWithPath("data.[].runnerProfile.name").type(STRING).description("러너 게시글의 러너 프로필 이름"), + fieldWithPath("data.[].runnerProfile.imageUrl").type(STRING).description("러너 게시글의 러너 프로필 이미지"), + fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), + fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") )) ); } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java index 5dd15fe5f..868ac7384 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOneApiTest.java @@ -3,16 +3,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.query.controller.RunnerPostQueryController; +import touch.baton.domain.tag.command.Tag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -30,7 +27,10 @@ import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; @@ -40,18 +40,8 @@ import static touch.baton.fixture.vo.DeadlineFixture.deadline; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerPostController.class) class RunnerPostReadOneApiTest extends RestdocsConfig { - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } - @DisplayName("러너 게시글 상세 조회 API") @Test void readByRunnerPostId() throws Exception { @@ -72,14 +62,14 @@ void readByRunnerPostId() throws Exception { final RunnerPost spyRunnerPost = spy(runnerPost); when(spyRunnerPost.getId()).thenReturn(1L); - when(runnerPostService.readByRunnerPostId(any())) + when(runnerPostQueryService.readByRunnerPostId(any())) .thenReturn(spyRunnerPost); - when(runnerPostService.readCountByRunnerPostId(any())) + when(runnerPostQueryService.countApplicantsByRunnerPostId(any())) .thenReturn(3L); final String token = getAccessTokenBySocialId(memberHyena.getSocialId().getValue()); - when(oauthMemberRepository.findBySocialId(any())) + when(oauthMemberCommandRepository.findBySocialId(any())) .thenReturn(Optional.ofNullable(memberHyena)); // then diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java index da8d9975e..7c272e67c 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java @@ -1,34 +1,26 @@ package touch.baton.document.runnerpost.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostReadController; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountMappingDto; -import touch.baton.domain.runnerpost.service.RunnerPostReadService; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.common.request.PageParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; +import touch.baton.domain.tag.command.Tag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.RunnerPostTagFixture; import touch.baton.fixture.domain.TagFixture; import java.time.LocalDateTime; import java.util.List; -import java.util.Map; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.spy; @@ -48,21 +40,8 @@ import static touch.baton.fixture.vo.DeadlineFixture.deadline; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerPostReadController.class) class RunnerPostReadSearchApiTest extends RestdocsConfig { - @MockBean - private RunnerPostReadService runnerPostReadService; - - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostReadController runnerPostReadController = new RunnerPostReadController(runnerPostReadService, runnerPostService); - restdocsSetUp(runnerPostReadController); - } - @DisplayName("태그 이름과 리뷰 상태를 조건으로 러너 게시글 페이징 조회 API") @Test void readRunnerPostsByTagNamesAndReviewStatus() throws Exception { @@ -78,29 +57,29 @@ void readRunnerPostsByTagNamesAndReviewStatus() throws Exception { given(spyRunnerPost.getId()).willReturn(1L); // when - final List runnerPosts = List.of(spyRunnerPost); - final PageRequest pageOne = PageRequest.of(1, 10); - final PageImpl pageRunnerPosts = new PageImpl<>(runnerPosts, pageOne, runnerPosts.size()); - when(runnerPostReadService.readRunnerPostByTagNameAndReviewStatus(any(Pageable.class), anyString(), any(ReviewStatus.class))) - .thenReturn(pageRunnerPosts); - - when(runnerPostReadService.readApplicantCountMappingByRunnerPostIds(anyList())) - .thenReturn(new ApplicantCountMappingDto(Map.of(1L, 0L))); + final List responses = List.of(RunnerPostResponse.Simple.of( + spyRunnerPost, + 0L, + List.of(RunnerPostTagFixture.create(spyRunnerPost, javaTag), RunnerPostTagFixture.create(spyRunnerPost, springTag)) + )); + final PageResponse pageResponse = PageResponse.of(responses, new PageParams(2L, 10)); + when(runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus(anyString(), any(PageParams.class), any(ReviewStatus.class))) + .thenReturn(pageResponse); // then - mockMvc.perform(get("/api/v1/posts/runner/tags/search") - .queryParam("size", String.valueOf(pageOne.getPageSize())) - .queryParam("page", String.valueOf(pageOne.getPageNumber())) - .queryParam("reviewStatus", ReviewStatus.NOT_STARTED.name()) - .queryParam("tagName", javaTag.getTagName().getValue())) + mockMvc.perform(get("/api/v1/posts/runner") + .queryParam("tagName", javaTag.getTagName().getValue()) + .queryParam("cursor", String.valueOf(1000L)) + .queryParam("limit", String.valueOf(10)) + .queryParam("reviewStatus", ReviewStatus.NOT_STARTED.name())) .andExpect(status().isOk()) .andExpect(content().contentType(APPLICATION_JSON)) .andDo(restDocs.document( queryParameters( - parameterWithName("size").description("페이지 사이즈"), - parameterWithName("page").description("페이지 번호"), - parameterWithName("reviewStatus").description("리뷰 상태"), - parameterWithName("tagName").description("태그 이름") + parameterWithName("cursor").description("(Optional) 이전 페이지 마지막 게시글 식별자값(id)"), + parameterWithName("limit").description("페이지 사이즈"), + parameterWithName("reviewStatus").description("(Optional) 리뷰 상태"), + parameterWithName("tagName").description("(Optional) 태그 이름") ), responseFields( fieldWithPath("data.[].runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), @@ -112,13 +91,8 @@ void readRunnerPostsByTagNamesAndReviewStatus() throws Exception { fieldWithPath("data.[].runnerProfile.name").type(STRING).description("러너 게시글의 러너 프로필 이름"), fieldWithPath("data.[].runnerProfile.imageUrl").type(STRING).description("러너 게시글의 러너 프로필 이미지"), fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), - fieldWithPath("pageInfo.isFirst").type(BOOLEAN).description("첫 번째 페이지인지"), - fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지인지"), - fieldWithPath("pageInfo.hasNext").type(BOOLEAN).description("다음 페이지가 있는지"), - fieldWithPath("pageInfo.totalPages").type(NUMBER).description("총 페이지 수"), - fieldWithPath("pageInfo.totalElements").type(NUMBER).description("총 데이터 수"), - fieldWithPath("pageInfo.currentPage").type(NUMBER).description("현재 페이지"), - fieldWithPath("pageInfo.currentSize").type(NUMBER).description("현재 페이지 데이터 수") + fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") )) ); } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java new file mode 100644 index 000000000..ea878765a --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java @@ -0,0 +1,115 @@ +package touch.baton.document.runnerpost.read; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.common.request.PageParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; +import touch.baton.domain.tag.command.Tag; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.RunnerPostTagFixture; +import touch.baton.fixture.domain.TagFixture; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.BOOLEAN; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.JsonFieldType.VARIES; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; +import static touch.baton.fixture.vo.TagNameFixture.tagName; + +class RunnerPostReadWithLoginedRunnerApiTest extends RestdocsConfig { + + @DisplayName("로그인한 러너가 작성한 러너 게시글 페이징 조회 API") + @Test + void readRunnerPostByLoginedRunnerAndReviewStatus() throws Exception { + // given + final String socialId = "ditooSocialId"; + final Member loginedMember = MemberFixture.createWithSocialId(socialId); + final Runner loginedRunner = RunnerFixture.createRunner(loginedMember); + final String token = getAccessTokenBySocialId(socialId); + + final Tag javaTag = TagFixture.create(tagName("자바")); + final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); + final RunnerPost runnerPost = RunnerPostFixture.create(loginedRunner, deadline, List.of(javaTag)); + + final Runner spyLoginedRunner = spy(loginedRunner); + given(oauthRunnerCommandRepository.joinByMemberSocialId(any())).willReturn(Optional.ofNullable(spyLoginedRunner)); + given(Objects.requireNonNull(spyLoginedRunner).getId()).willReturn(1L); + final RunnerPost spyRunnerPost = spy(runnerPost); + given(spyRunnerPost.getId()).willReturn(1L); + + // when + final List responses = List.of(RunnerPostResponse.SimpleByRunner.of( + spyRunnerPost, + 0L, + List.of(RunnerPostTagFixture.create(spyRunnerPost, javaTag)) + )); + final PageResponse pageResponse = PageResponse.of(responses, new PageParams(2L, 10)); + when(runnerPostQueryService.pageRunnerPostByRunnerIdAndReviewStatus(any(PageParams.class), eq(1L), eq(ReviewStatus.NOT_STARTED))) + .thenReturn(pageResponse); + + // then + mockMvc.perform(get("/api/v1/posts/runner/me/runner") + .header(AUTHORIZATION, "Bearer " + token) + .queryParam("cursor", String.valueOf(1000L)) + .queryParam("limit", String.valueOf(10)) + .queryParam("reviewStatus", ReviewStatus.NOT_STARTED.name())) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andDo(restDocs.document( + requestHeaders( + headerWithName(AUTHORIZATION).description("Bearer JWT") + ), + queryParameters( + parameterWithName("cursor").description("(Optional) 이전 페이지 마지막 게시글 식별자값(id)"), + parameterWithName("limit").description("페이지 사이즈"), + parameterWithName("reviewStatus").description("(Optional) 리뷰 상태") + ), + responseFields( + fieldWithPath("data.[].runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), + fieldWithPath("data.[].title").type(STRING).description("러너 게시글의 제목"), + fieldWithPath("data.[].deadline").type(STRING).description("러너 게시글의 마감 기한"), + fieldWithPath("data.[].watchedCount").type(NUMBER).description("러너 게시글의 조회수"), + fieldWithPath("data.[].applicantCount").type(NUMBER).description("러너 게시글에 신청한 서포터 수"), + fieldWithPath("data.[].reviewStatus").type(STRING).description("러너 게시글의 리뷰 상태"), + fieldWithPath("data.[].isReviewed").type(BOOLEAN).description("러너 게시글의 리뷰 완료 여부"), + fieldWithPath("data.[].supporterId").type(VARIES) + .optional() + .description("서포터 id (서포터가 존재할 때 NUMBER, 아닌 경우에 NULL)"), + fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), + fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") + )) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java index 9df156ea0..ed7f0194f 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java @@ -1,34 +1,33 @@ package touch.baton.document.runnerpost.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.common.request.PageParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; +import touch.baton.domain.tag.command.Tag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.RunnerPostTagFixture; import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.TagFixture; import java.time.LocalDateTime; import java.util.List; +import java.util.Objects; import java.util.Optional; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static org.springframework.http.HttpHeaders.AUTHORIZATION; @@ -46,67 +45,48 @@ import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static touch.baton.fixture.vo.CompanyFixture.company; import static touch.baton.fixture.vo.DeadlineFixture.deadline; -import static touch.baton.fixture.vo.GithubUrlFixture.githubUrl; -import static touch.baton.fixture.vo.ImageUrlFixture.imageUrl; -import static touch.baton.fixture.vo.MemberNameFixture.memberName; -import static touch.baton.fixture.vo.OauthIdFixture.oauthId; -import static touch.baton.fixture.vo.SocialIdFixture.socialId; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerPostController.class) -public class RunnerPostReadWithLoginedSupporterApiTest extends RestdocsConfig { - - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } +class RunnerPostReadWithLoginedSupporterApiTest extends RestdocsConfig { @DisplayName("로그인한 서포터가 참여한 러너 게시글 페이징 조회 API") @Test - void readRunnerPostsByLoginedSupporterAndReviewStatus() throws Exception { + void readRunnerPostByLoginedSupporterAndReviewStatus() throws Exception { // given - final Runner runnerJudy = RunnerFixture.createRunner(MemberFixture.createJudy()); final String socialId = "ditooSocialId"; - final Member loginedMember = MemberFixture.create( - memberName("디투"), - socialId(socialId), - oauthId("abcd"), - githubUrl("naver.com"), - company("우아한테크코스"), - imageUrl("profile.jpg") - ); + final Member loginedMember = MemberFixture.createWithSocialId(socialId); final Supporter loginedSupporter = SupporterFixture.create(loginedMember); final String token = getAccessTokenBySocialId(socialId); + final Runner runner = RunnerFixture.createRunner(MemberFixture.createEthan()); + final Tag javaTag = TagFixture.create(tagName("자바")); final Deadline deadline = deadline(LocalDateTime.now().plusHours(100)); - final RunnerPost runnerPost = RunnerPostFixture.create(runnerJudy, deadline, List.of(javaTag)); + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline, List.of(javaTag)); runnerPost.assignSupporter(loginedSupporter); - // when - final RunnerPost spyRunnerPost = spy(runnerPost); final Supporter spyLoginedSupporter = spy(loginedSupporter); - when(oauthSupporterRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(spyLoginedSupporter)); - when(spyRunnerPost.getId()).thenReturn(1L); + given(oauthSupporterCommandRepository.joinByMemberSocialId(any())).willReturn(Optional.ofNullable(spyLoginedSupporter)); + given(Objects.requireNonNull(spyLoginedSupporter).getId()).willReturn(1L); + final RunnerPost spyRunnerPost = spy(runnerPost); + given(spyRunnerPost.getId()).willReturn(1L); - final List runnerPosts = List.of(spyRunnerPost); - final PageRequest pageOne = PageRequest.of(1, 10); - final PageImpl pageRunnerPosts = new PageImpl<>(runnerPosts, pageOne, runnerPosts.size()); - when(runnerPostService.readRunnerPostsBySupporterIdAndReviewStatus(any(), any(), any())) - .thenReturn(pageRunnerPosts); - when(runnerPostService.readCountsByRunnerPostIds(anyList())).thenReturn(List.of(1L)); + // when + final List responses = List.of(RunnerPostResponse.Simple.of( + spyRunnerPost, + 0L, + List.of(RunnerPostTagFixture.create(spyRunnerPost, javaTag)) + )); + final PageResponse pageResponse = PageResponse.of(responses, new PageParams(2L, 10)); + when(runnerPostQueryService.pageRunnerPostBySupporterIdAndReviewStatus(any(PageParams.class), eq(1L), eq(ReviewStatus.IN_PROGRESS))) + .thenReturn(pageResponse); // then mockMvc.perform(get("/api/v1/posts/runner/me/supporter") .header(AUTHORIZATION, "Bearer " + token) - .queryParam("size", String.valueOf(pageOne.getPageSize())) - .queryParam("page", String.valueOf(pageOne.getPageNumber())) + .queryParam("cursor", String.valueOf(1000L)) + .queryParam("limit", String.valueOf(10)) .queryParam("reviewStatus", ReviewStatus.IN_PROGRESS.name())) .andExpect(status().isOk()) .andExpect(content().contentType(APPLICATION_JSON)) @@ -115,25 +95,22 @@ void readRunnerPostsByLoginedSupporterAndReviewStatus() throws Exception { headerWithName(AUTHORIZATION).description("Bearer JWT") ), queryParameters( - parameterWithName("size").description("페이지 사이즈"), - parameterWithName("page").description("페이지 번호"), - parameterWithName("reviewStatus").description("리뷰 상태") + parameterWithName("cursor").description("(Optional) 이전 페이지 마지막 게시글 식별자값(id)"), + parameterWithName("limit").description("페이지 사이즈"), + parameterWithName("reviewStatus").description("(Optional) 리뷰 상태") ), responseFields( - fieldWithPath("pageInfo.isFirst").type(BOOLEAN).description("첫 번째 페이지인지"), - fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 인지"), - fieldWithPath("pageInfo.hasNext").type(BOOLEAN).description("다음 페이지가 있는지"), - fieldWithPath("pageInfo.totalPages").type(NUMBER).description("총 페이지 수"), - fieldWithPath("pageInfo.totalElements").type(NUMBER).description("총 데이터 수"), - fieldWithPath("pageInfo.currentPage").type(NUMBER).description("현재 페이지 번호"), - fieldWithPath("pageInfo.currentSize").type(NUMBER).description("현재 페이지 데이터 수"), fieldWithPath("data.[].runnerPostId").type(NUMBER).description("러너 게시글 식별자값(id)"), - fieldWithPath("data.[].title").type(STRING).description("러너 게시글 제목"), + fieldWithPath("data.[].title").type(STRING).description("러너 게시글의 제목"), fieldWithPath("data.[].deadline").type(STRING).description("러너 게시글의 마감 기한"), - fieldWithPath("data.[].tags").type(ARRAY).description("러너 게시글 태그 목록"), fieldWithPath("data.[].watchedCount").type(NUMBER).description("러너 게시글의 조회수"), fieldWithPath("data.[].applicantCount").type(NUMBER).description("러너 게시글에 신청한 서포터 수"), - fieldWithPath("data.[].reviewStatus").type(STRING).description("러너 게시글 리뷰 상태") + fieldWithPath("data.[].reviewStatus").type(STRING).description("러너 게시글의 리뷰 상태"), + fieldWithPath("data.[].runnerProfile.name").type(STRING).description("러너 게시글의 러너 프로필 이름"), + fieldWithPath("data.[].runnerProfile.imageUrl").type(STRING).description("러너 게시글의 러너 프로필 이미지"), + fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), + fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") )) ); } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostUpdateApplicantCancelationApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostUpdateApplicantCancelationApiTest.java index 876023abc..386e646a4 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostUpdateApplicantCancelationApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostUpdateApplicantCancelationApiTest.java @@ -1,18 +1,13 @@ package touch.baton.document.runnerpost.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -42,17 +37,7 @@ import static touch.baton.fixture.vo.OauthIdFixture.oauthId; import static touch.baton.fixture.vo.SocialIdFixture.socialId; -@WebMvcTest(RunnerPostController.class) -public class RunnerPostUpdateApplicantCancelationApiTest extends RestdocsConfig { - - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } +class RunnerPostUpdateApplicantCancelationApiTest extends RestdocsConfig { @DisplayName("러너 게시글에 리뷰 제안한 서포터가 리뷰 제안 철회 API") @Test @@ -76,8 +61,8 @@ void updateSupporterCancelRunnerPost() throws Exception { // when when(spyRunnerPost.getId()).thenReturn(1L); - when(oauthSupporterRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporter)); - runnerPostService.deleteSupporterRunnerPost(any(), eq(1L)); + when(oauthSupporterCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporter)); + runnerPostCommandService.deleteSupporterRunnerPost(any(), eq(1L)); // then mockMvc.perform(patch("/api/v1/posts/runner/{runnerPostId}/cancelation", 1L) diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/SupporterRunnerPostReadApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/SupporterRunnerPostReadApiTest.java index c1ba8347d..30da50277 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/SupporterRunnerPostReadApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/SupporterRunnerPostReadApiTest.java @@ -1,20 +1,15 @@ package touch.baton.document.runnerpost.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.tag.command.Tag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -36,7 +31,9 @@ import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.payload.JsonFieldType.*; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; @@ -46,18 +43,8 @@ import static touch.baton.fixture.vo.DeadlineFixture.deadline; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(RunnerPostController.class) class SupporterRunnerPostReadApiTest extends RestdocsConfig { - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - final RunnerPostController runnerPostController = new RunnerPostController(runnerPostService); - restdocsSetUp(runnerPostController); - } - @DisplayName("러너 게시글의 지원한 서포터 목록 조회 API") @Test void readSupporterRunnerPostsByRunnerPostId() throws Exception { @@ -77,8 +64,8 @@ void readSupporterRunnerPostsByRunnerPostId() throws Exception { // when given(spySupporter.getId()).willReturn(1L); given(spyRunnerPost.getId()).willReturn(1L); - given(runnerPostService.readSupporterRunnerPostsByRunnerPostId(any(), any())).willReturn(List.of(supporterRunnerPost)); - when(oauthRunnerRepository.joinByMemberSocialId(notNull())) + given(runnerPostQueryService.readSupporterRunnerPostsByRunnerPostId(any(), any())).willReturn(List.of(supporterRunnerPost)); + when(oauthRunnerCommandRepository.joinByMemberSocialId(notNull())) .thenReturn(Optional.ofNullable(runner)); // then diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/TagReadApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/TagReadApiTest.java index 721b370e6..18e65d9b5 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/TagReadApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/TagReadApiTest.java @@ -1,20 +1,15 @@ package touch.baton.document.runnerpost.read; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.controller.TagController; -import touch.baton.domain.tag.service.TagService; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; import touch.baton.fixture.domain.TagFixture; import java.util.List; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import static org.springframework.http.MediaType.APPLICATION_JSON; @@ -29,18 +24,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static touch.baton.fixture.vo.TagNameFixture.tagName; -@WebMvcTest(TagController.class) class TagReadApiTest extends RestdocsConfig { - @MockBean - private TagService tagService; - - @BeforeEach - void setUp() { - final TagController tagController = new TagController(tagService); - restdocsSetUp(tagController); - } - @DisplayName("태그 검색 API") @Test void readTagsByReducedName() throws Exception { @@ -51,7 +36,7 @@ void readTagsByReducedName() throws Exception { final Tag javascriptTagSpy = spy(javascriptTag); // when - when(tagService.readTagsByReducedName("java")) + when(tagQueryService.readTagsByReducedName(TagReducedName.nullableInstance("java"), 10)) .thenReturn(List.of(javaTagSpy, javascriptTagSpy)); when(javaTagSpy.getId()) .thenReturn(1L); diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/update/RunnerPostUpdateApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/update/RunnerPostUpdateApiTest.java index 7889c82d2..515600216 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/update/RunnerPostUpdateApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/update/RunnerPostUpdateApiTest.java @@ -1,17 +1,12 @@ package touch.baton.document.runnerpost.update; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; import touch.baton.config.RestdocsConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.controller.RunnerPostController; -import touch.baton.domain.runnerpost.service.RunnerPostService; -import touch.baton.domain.runnerpost.service.dto.RunnerPostUpdateRequest; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.SupporterFixture; @@ -21,7 +16,7 @@ import static org.apache.http.HttpHeaders.CONTENT_TYPE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.http.HttpHeaders.LOCATION; @@ -32,20 +27,10 @@ import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -@WebMvcTest(RunnerPostController.class) -public class RunnerPostUpdateApiTest extends RestdocsConfig { - - @MockBean - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - restdocsSetUp(new RunnerPostController(runnerPostService)); - } +class RunnerPostUpdateApiTest extends RestdocsConfig { @DisplayName("제안한 서포터 목록 중에서 서포터로 선택하는 API") @Test @@ -59,8 +44,8 @@ void updateRunnerPostSupporter() throws Exception { final RunnerPostUpdateRequest.SelectSupporter request = new RunnerPostUpdateRequest.SelectSupporter(1L); // when - willDoNothing().given(runnerPostService).updateRunnerPostAppliedSupporter(any(Runner.class), anyLong(), any(RunnerPostUpdateRequest.SelectSupporter.class)); - when(oauthRunnerRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(ditooRunner)); + doNothing().when(runnerPostCommandService).updateRunnerPostAppliedSupporter(any(Runner.class), anyLong(), any(RunnerPostUpdateRequest.SelectSupporter.class)); + when(oauthRunnerCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(ditooRunner)); // then mockMvc.perform(patch("/api/v1/posts/runner/{runnerPostId}/supporters", 1L) @@ -74,7 +59,7 @@ void updateRunnerPostSupporter() throws Exception { requestHeaders(headerWithName(AUTHORIZATION).description("Bearer JWT"), headerWithName(CONTENT_TYPE).description(APPLICATION_JSON_VALUE)), responseHeaders(headerWithName(LOCATION).description("Redirect URI")) - )).andDo(print()); + )); } @DisplayName("서포터 리뷰 완료 API") @@ -87,8 +72,8 @@ void updateRunnerPostReviewStatusDone() throws Exception { final String accessToken = getAccessTokenBySocialId(ditooSocialId); // when - willDoNothing().given(runnerPostService).updateRunnerPostReviewStatusDone(anyLong(), any(Supporter.class)); - when(oauthSupporterRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporter)); + doNothing().when(runnerPostCommandService).updateRunnerPostReviewStatusDone(anyLong(), any(Supporter.class)); + when(oauthSupporterCommandRepository.joinByMemberSocialId(any())).thenReturn(Optional.ofNullable(supporter)); // then mockMvc.perform(patch("/api/v1/posts/runner/{runnerPostId}/done", 1L) diff --git a/backend/baton/src/test/java/touch/baton/domain/common/vo/DescriptionTest.java b/backend/baton/src/test/java/touch/baton/domain/common/vo/DescriptionTest.java index 88ec103e2..98583b0dd 100644 --- a/backend/baton/src/test/java/touch/baton/domain/common/vo/DescriptionTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/common/vo/DescriptionTest.java @@ -2,7 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import touch.baton.domain.feedback.vo.Description; +import touch.baton.domain.feedback.command.vo.Description; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/common/vo/TitleTest.java b/backend/baton/src/test/java/touch/baton/domain/common/vo/TitleTest.java index a27af7c9f..7c12839ff 100644 --- a/backend/baton/src/test/java/touch/baton/domain/common/vo/TitleTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/common/vo/TitleTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.runnerpost.command.vo.Title; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/common/vo/WatchedCountTest.java b/backend/baton/src/test/java/touch/baton/domain/common/vo/WatchedCountTest.java index 85542d55d..eeaef4a2f 100644 --- a/backend/baton/src/test/java/touch/baton/domain/common/vo/WatchedCountTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/common/vo/WatchedCountTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; import static org.assertj.core.api.Assertions.assertThat; diff --git a/backend/baton/src/test/java/touch/baton/domain/feedback/repository/SupporterFeedbackRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/feedback/repository/SupporterFeedbackCommandRepositoryTest.java similarity index 71% rename from backend/baton/src/test/java/touch/baton/domain/feedback/repository/SupporterFeedbackRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/domain/feedback/repository/SupporterFeedbackCommandRepositoryTest.java index acbb188fe..1dd932980 100644 --- a/backend/baton/src/test/java/touch/baton/domain/feedback/repository/SupporterFeedbackRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/feedback/repository/SupporterFeedbackCommandRepositoryTest.java @@ -5,11 +5,12 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.feedback.SupporterFeedback; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.feedback.command.SupporterFeedback; +import touch.baton.domain.feedback.command.repository.SupporterFeedbackCommandRepository; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -21,12 +22,12 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; -class SupporterFeedbackRepositoryTest extends RepositoryTestConfig { +class SupporterFeedbackCommandRepositoryTest extends RepositoryTestConfig { @Autowired private EntityManager em; @Autowired - private SupporterFeedbackRepository supporterFeedbackRepository; + private SupporterFeedbackCommandRepository supporterFeedbackCommandRepository; @DisplayName("러너 게시글 아이디와 서포터 아이디로 서포터 피드백 존재 유무를 확인할 수 있다.") @Test @@ -58,8 +59,8 @@ void existsByRunnerPostIdAndSupporterId() { // when, then assertSoftly(softly -> { - softly.assertThat(supporterFeedbackRepository.existsByRunnerPostIdAndSupporterId(runnerPost.getId(), reviewedSupporter.getId())).isTrue(); - softly.assertThat(supporterFeedbackRepository.existsByRunnerPostIdAndSupporterId(runnerPost.getId(), notReviewedSupporter.getId())).isFalse(); + softly.assertThat(supporterFeedbackCommandRepository.existsByRunnerPostIdAndSupporterId(runnerPost.getId(), reviewedSupporter.getId())).isTrue(); + softly.assertThat(supporterFeedbackCommandRepository.existsByRunnerPostIdAndSupporterId(runnerPost.getId(), notReviewedSupporter.getId())).isFalse(); }); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/feedback/service/FeedbackServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/feedback/service/FeedbackCommandServiceTest.java similarity index 55% rename from backend/baton/src/test/java/touch/baton/domain/feedback/service/FeedbackServiceTest.java rename to backend/baton/src/test/java/touch/baton/domain/feedback/service/FeedbackCommandServiceTest.java index 7dce9f61c..55713aeb9 100644 --- a/backend/baton/src/test/java/touch/baton/domain/feedback/service/FeedbackServiceTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/feedback/service/FeedbackCommandServiceTest.java @@ -4,11 +4,13 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.feedback.command.service.FeedbackCommandService; +import touch.baton.domain.feedback.command.service.dto.SupporterFeedBackCreateRequest; import touch.baton.domain.feedback.exception.FeedbackBusinessException; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -20,9 +22,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; -class FeedbackServiceTest extends ServiceTestConfig { +class FeedbackCommandServiceTest extends ServiceTestConfig { - private FeedbackService feedbackService; + private FeedbackCommandService feedbackCommandService; private Runner exactRunner; private RunnerPost runnerPost; private SupporterFeedBackCreateRequest request; @@ -30,12 +32,12 @@ class FeedbackServiceTest extends ServiceTestConfig { @BeforeEach void setUp() { - feedbackService = new FeedbackService(supporterFeedbackRepository, runnerPostRepository, supporterRepository); - final Member ethan = memberRepository.save(MemberFixture.createEthan()); - exactRunner = runnerRepository.save(RunnerFixture.createRunner(ethan)); - final Member ditoo = memberRepository.save(MemberFixture.createDitoo()); - reviewedSupporter = supporterRepository.save(SupporterFixture.create(ditoo)); - runnerPost = runnerPostRepository.save(RunnerPostFixture.create(exactRunner, reviewedSupporter)); + feedbackCommandService = new FeedbackCommandService(supporterFeedbackCommandRepository, runnerPostCommandRepository, supporterCommandRepository); + final Member ethan = memberCommandRepository.save(MemberFixture.createEthan()); + exactRunner = runnerQueryRepository.save(RunnerFixture.createRunner(ethan)); + final Member ditoo = memberCommandRepository.save(MemberFixture.createDitoo()); + reviewedSupporter = supporterQueryRepository.save(SupporterFixture.create(ditoo)); + runnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create(exactRunner, reviewedSupporter)); request = new SupporterFeedBackCreateRequest("GOOD", List.of("코드리뷰가 맛있어요.", "말투가 친절해요."), reviewedSupporter.getId(), runnerPost.getId()); } @@ -44,7 +46,7 @@ void setUp() { @Test void createSupporterFeedback() { // when - final Long expected = feedbackService.createSupporterFeedback(exactRunner, request); + final Long expected = feedbackCommandService.createSupporterFeedback(exactRunner, request); // then assertSoftly(softly -> { @@ -57,11 +59,11 @@ void createSupporterFeedback() { @Test void fail_createSupporterFeedback_if_not_owner_runner() { // given - final Member differentMember = memberRepository.save(MemberFixture.createHyena()); - final Runner notOwner = runnerRepository.save(RunnerFixture.createRunner(differentMember)); + final Member differentMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner notOwner = runnerQueryRepository.save(RunnerFixture.createRunner(differentMember)); // when, then - assertThatThrownBy(() -> feedbackService.createSupporterFeedback(notOwner, request)) + assertThatThrownBy(() -> feedbackCommandService.createSupporterFeedback(notOwner, request)) .isInstanceOf(FeedbackBusinessException.class); } @@ -69,12 +71,12 @@ void fail_createSupporterFeedback_if_not_owner_runner() { @Test void fail_createSupporterFeedback_if_not_review_supporter_runner() { // given - final Member differentMember = memberRepository.save(MemberFixture.createHyena()); - final Supporter notReviewSupporter = supporterRepository.save(SupporterFixture.create(differentMember)); + final Member differentMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter notReviewSupporter = supporterQueryRepository.save(SupporterFixture.create(differentMember)); final SupporterFeedBackCreateRequest notReviewSupporterRequest = new SupporterFeedBackCreateRequest("GOOD", new ArrayList<>(), notReviewSupporter.getId(), runnerPost.getId()); // when, then - assertThatThrownBy(() -> feedbackService.createSupporterFeedback(exactRunner, notReviewSupporterRequest)) + assertThatThrownBy(() -> feedbackCommandService.createSupporterFeedback(exactRunner, notReviewSupporterRequest)) .isInstanceOf(FeedbackBusinessException.class); } @@ -83,10 +85,10 @@ void fail_createSupporterFeedback_if_not_review_supporter_runner() { void fail_createSupporterFeedback_if_already_reviewed_supporter() { // given final SupporterFeedBackCreateRequest supporterFeedBackCreateRequest = new SupporterFeedBackCreateRequest("GOOD", new ArrayList<>(), reviewedSupporter.getId(), runnerPost.getId()); - feedbackService.createSupporterFeedback(exactRunner, supporterFeedBackCreateRequest); + feedbackCommandService.createSupporterFeedback(exactRunner, supporterFeedBackCreateRequest); // when, then - assertThatThrownBy(() -> feedbackService.createSupporterFeedback(exactRunner, supporterFeedBackCreateRequest)) + assertThatThrownBy(() -> feedbackCommandService.createSupporterFeedback(exactRunner, supporterFeedBackCreateRequest)) .isInstanceOf(FeedbackBusinessException.class); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java b/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java index d1fe576e9..2732dfb11 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/MemberTest.java @@ -4,18 +4,17 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; import touch.baton.domain.member.exception.MemberDomainException; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; import touch.baton.fixture.domain.MemberFixture; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; class MemberTest { diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/CompanyTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/CompanyTest.java index e9312518e..9b6cb3b7c 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/vo/CompanyTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/CompanyTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.vo.Company; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/GithubUrlTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/GithubUrlTest.java index 28c73ca0f..2bf35a995 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/vo/GithubUrlTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/GithubUrlTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.vo.GithubUrl; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/ImageUrlTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/ImageUrlTest.java index 511c551c4..a03baa970 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/vo/ImageUrlTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/ImageUrlTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.vo.ImageUrl; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java index 6cf5fef9c..fd5058eb7 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/MemberNameTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.vo.MemberName; import static org.assertj.core.api.Assertions.assertThat; diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/OauthIdTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/OauthIdTest.java index 20582fcc6..9546692f7 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/vo/OauthIdTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/OauthIdTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.vo.OauthId; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/SocialIdTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/SocialIdTest.java index 818617c06..2677e9886 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/vo/SocialIdTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/SocialIdTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.vo.SocialId; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/NotificationTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/NotificationTest.java new file mode 100644 index 000000000..3b52e7a6a --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/NotificationTest.java @@ -0,0 +1,207 @@ +package touch.baton.domain.notification.command; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.notification.command.vo.IsRead; +import touch.baton.domain.notification.command.vo.NotificationMessage; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.notification.command.vo.NotificationTitle; +import touch.baton.domain.notification.command.vo.NotificationType; +import touch.baton.domain.notification.exception.NotificationDomainException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +class NotificationTest { + + private static final Member owner = Member.builder() + .memberName(new MemberName("사용자 테스트용 이름")) + .socialId(new SocialId("사용자 테스트용 소셜 아이디")) + .oauthId(new OauthId("사용자 테스트용 오어스 아이디")) + .githubUrl(new GithubUrl("https://github.com/사용자_테스트용_깃허브_주소")) + .company(new Company("사용자 테스트용 회사명")) + .imageUrl(new ImageUrl("https://사용자_테스트용_이미지_주소")) + .build(); + + private static final Member notOwner = Member.builder() + .memberName(new MemberName("사용자 테스트용 이름")) + .socialId(new SocialId("사용자 테스트용 소셜 아이디")) + .oauthId(new OauthId("사용자 테스트용 오어스 아이디")) + .githubUrl(new GithubUrl("https://github.com/사용자_테스트용_깃허브_주소")) + .company(new Company("사용자 테스트용 회사명")) + .imageUrl(new ImageUrl("https://사용자_테스트용_이미지_주소")) + .build(); + + @DisplayName("생성 테스트") + @Nested + class Create { + + @DisplayName("성공한다") + @Test + void success() { + assertThatCode(() -> Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(owner) + .build() + ).doesNotThrowAnyException(); + } + + @DisplayName("notificationTitle 에 null 이 들어갈 경우 예외가 발생한다.") + @Test + void fail_if_notificationTitle_is_null() { + assertThatThrownBy(() -> Notification.builder() + .notificationTitle(null) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(owner) + .build() + ).isInstanceOf(NotificationDomainException.class); + } + + @DisplayName("notificationMessage 에 null 이 들어갈 경우 예외가 발생한다.") + @Test + void fail_if_notificationMessage_is_null() { + assertThatThrownBy(() -> Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(null) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(owner) + .build() + ).isInstanceOf(NotificationDomainException.class); + } + + @DisplayName("notificationType() 에 null 이 들어갈 경우 예외가 발생한다.") + @Test + void fail_if_notificationType_is_null() { + assertThatThrownBy(() -> Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(null) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(owner) + .build() + ).isInstanceOf(NotificationDomainException.class); + } + + @DisplayName("notificationReferencedId() 에 null 이 들어갈 경우 예외가 발생한다.") + @Test + void fail_if_notificationReferencedId_is_null() { + assertThatThrownBy(() -> Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(null) + .isRead(IsRead.asUnRead()) + .member(owner) + .build() + ).isInstanceOf(NotificationDomainException.class); + } + + @DisplayName("isRead 에 null 이 들어갈 경우 예외가 발생한다.") + @Test + void fail_if_isRead_is_null() { + assertThatThrownBy(() -> Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(null) + .member(owner) + .build() + ).isInstanceOf(NotificationDomainException.class); + } + + @DisplayName("member 에 null 이 들어갈 경우 예외가 발생한다.") + @Test + void fail_if_member_is_null() { + assertThatThrownBy(() -> Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(null) + .build() + ).isInstanceOf(NotificationDomainException.class); + } + } + + @DisplayName("알림 여부를 수정할 때 주인(Member) 일 경우 읽음 상태로 수정할 수 있다.") + @Test + void success_markAsRead() { + // given + final Notification notification = Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(owner) + .build(); + + // when + notification.markAsRead(owner); + + // then + final boolean actual = notification.getIsRead().getValue(); + + assertThat(actual).isTrue(); + } + + @DisplayName("알림 여부를 수정할 때 주인(Member) 이 아닐 경우 예외가 발생한다.") + @Test + void fail_markAsRead_when_member_isNotOwner() { + // given + final Notification notification = Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(owner) + .build(); + + // when & then + assertThatThrownBy(() -> notification.markAsRead(notOwner)) + .isInstanceOf(NotificationDomainException.class); + } + + @DisplayName("알림의 주인(Member) 이 아닌지 확인한다.") + @Test + void isNotOwner() { + // given + final Notification notification = Notification.builder() + .notificationTitle(new NotificationTitle("알림 테스트용 제목")) + .notificationMessage(new NotificationMessage("알림 테스트용 내용")) + .notificationType(NotificationType.RUNNER_POST) + .notificationReferencedId(new NotificationReferencedId(1L)) + .isRead(IsRead.asUnRead()) + .member(owner) + .build(); + + // when & then + assertSoftly(softly -> { + softly.assertThat(notification.isNotOwner(owner)).isFalse(); + softly.assertThat(notification.isNotOwner(notOwner)).isTrue(); + }); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/event/NotificationEventListenerTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/event/NotificationEventListenerTest.java new file mode 100644 index 000000000..d88091d78 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/event/NotificationEventListenerTest.java @@ -0,0 +1,148 @@ +package touch.baton.domain.notification.command.event; + +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 touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.repository.NotificationCommandRepository; +import touch.baton.domain.notification.command.vo.NotificationType; +import touch.baton.domain.notification.query.repository.NotificationQuerydslRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.event.RunnerPostApplySupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostAssignSupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostReviewStatusDoneEvent; +import touch.baton.domain.runnerpost.query.repository.RunnerPostQueryRepository; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.List; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +class NotificationEventListenerTest extends RepositoryTestConfig { + + private NotificationEventListener notificationEventListener; + + @Autowired + private NotificationQuerydslRepository notificationQuerydslRepository; + + @BeforeEach + void setUp(@Autowired NotificationCommandRepository notificationCommandRepository, + @Autowired RunnerPostQueryRepository runnerPostQueryRepository + ) { + notificationEventListener = new NotificationEventListener(notificationCommandRepository, runnerPostQueryRepository); + } + + @DisplayName("러너 게시글에 서포터가 지원했다는 알림을 생성한다.") + @Test + void subscribeRunnerPostApplySupporterEvent() { + // given + final Runner targetRunner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(targetRunner); + + // when + final RunnerPostApplySupporterEvent event = new RunnerPostApplySupporterEvent(runnerPost.getId()); + notificationEventListener.subscribeRunnerPostApplySupporterEvent(event); + + // then + final List actualNotifications = notificationQuerydslRepository.findByMemberId(targetRunner.getMember().getId(), 10); + + final String expectedNotificationTitle = "서포터의 제안이 왔습니다."; + final String expectedNotificationMessage = String.format("관련 게시글 - %s", runnerPost.getTitle().getValue()); + final NotificationType expectedNotificationType = NotificationType.RUNNER_POST; + final Long expectedReferencedId = runnerPost.getId(); + final boolean expectedIsRead = false; + final Member expectedMember = targetRunner.getMember(); + + assertSoftly(softly -> { + softly.assertThat(actualNotifications).hasSize(1); + final Notification actual = actualNotifications.get(0); + + softly.assertThat(actual.getId()).isPositive(); + softly.assertThat(actual.getNotificationTitle().getValue()).isEqualTo(expectedNotificationTitle); + softly.assertThat(actual.getNotificationMessage().getValue()).isEqualTo(expectedNotificationMessage); + softly.assertThat(actual.getNotificationType()).isEqualTo(expectedNotificationType); + softly.assertThat(actual.getNotificationReferencedId().getValue()).isEqualTo(expectedReferencedId); + softly.assertThat(actual.getIsRead().getValue()).isEqualTo(expectedIsRead); + softly.assertThat(actual.getMember()).isEqualTo(expectedMember); + }); + } + + @DisplayName("러너 게시글 리뷰 상태가 DONE 으로 업데이트 되었다는 알림을 생성한다.") + @Test + void subscribeRunnerPostReviewStatusDoneEvent() { + // given + final Runner targetRunner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(targetRunner); + + // when + final RunnerPostReviewStatusDoneEvent event = new RunnerPostReviewStatusDoneEvent(runnerPost.getId()); + notificationEventListener.subscribeRunnerPostReviewStatusDoneEvent(event); + + // then + final List actualNotifications = notificationQuerydslRepository.findByMemberId(targetRunner.getMember().getId(), 10); + + final String expectedNotificationTitle = "코드 리뷰 상태가 완료로 변경되었습니다."; + final String expectedNotificationMessage = String.format("관련 게시글 - %s", runnerPost.getTitle().getValue()); + final NotificationType expectedNotificationType = NotificationType.RUNNER_POST; + final Long expectedReferencedId = runnerPost.getId(); + final boolean expectedIsRead = false; + final Member expectedMember = targetRunner.getMember(); + + assertSoftly(softly -> { + softly.assertThat(actualNotifications).hasSize(1); + final Notification actual = actualNotifications.get(0); + + softly.assertThat(actual.getId()).isPositive(); + softly.assertThat(actual.getNotificationTitle().getValue()).isEqualTo(expectedNotificationTitle); + softly.assertThat(actual.getNotificationMessage().getValue()).isEqualTo(expectedNotificationMessage); + softly.assertThat(actual.getNotificationType()).isEqualTo(expectedNotificationType); + softly.assertThat(actual.getNotificationReferencedId().getValue()).isEqualTo(expectedReferencedId); + softly.assertThat(actual.getIsRead().getValue()).isEqualTo(expectedIsRead); + softly.assertThat(actual.getMember()).isEqualTo(expectedMember); + }); + } + + @DisplayName("러너 게시글에 서포터를 할당했다는 알림을 생성한다.") + @Test + void subscribeRunnerPostAssignSupporterEvent() { + // given + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter targetSupporter = persistSupporter(MemberFixture.createEthan()); + + persistApplicant(targetSupporter, runnerPost); + persistAssignSupporter(targetSupporter, runnerPost); + + // when + final RunnerPostAssignSupporterEvent event = new RunnerPostAssignSupporterEvent(runnerPost.getId()); + notificationEventListener.subscribeRunnerPostAssignSupporterEvent(event); + + // then + final List actualNotifications = notificationQuerydslRepository.findByMemberId(targetSupporter.getMember().getId(), 10); + + final String expectedNotificationTitle = "코드 리뷰 매칭이 완료되었습니다."; + final String expectedNotificationMessage = String.format("관련 게시글 - %s", runnerPost.getTitle().getValue()); + final NotificationType expectedNotificationType = NotificationType.RUNNER_POST; + final Long expectedReferencedId = runnerPost.getId(); + final boolean expectedIsRead = false; + final Member expectedMember = targetSupporter.getMember(); + + assertSoftly(softly -> { + softly.assertThat(actualNotifications).hasSize(1); + final Notification actual = actualNotifications.get(0); + + softly.assertThat(actual.getId()).isPositive(); + softly.assertThat(actual.getNotificationTitle().getValue()).isEqualTo(expectedNotificationTitle); + softly.assertThat(actual.getNotificationMessage().getValue()).isEqualTo(expectedNotificationMessage); + softly.assertThat(actual.getNotificationType()).isEqualTo(expectedNotificationType); + softly.assertThat(actual.getNotificationReferencedId().getValue()).isEqualTo(expectedReferencedId); + softly.assertThat(actual.getIsRead().getValue()).isEqualTo(expectedIsRead); + softly.assertThat(actual.getMember()).isEqualTo(expectedMember); + }); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/repository/NotificationCommandRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/repository/NotificationCommandRepositoryTest.java new file mode 100644 index 000000000..219bb5c91 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/repository/NotificationCommandRepositoryTest.java @@ -0,0 +1,39 @@ +package touch.baton.domain.notification.command.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.fixture.domain.MemberFixture; + +import static org.assertj.core.api.Assertions.assertThat; +import static touch.baton.fixture.vo.NotificationReferencedIdFixture.notificationReferencedId; + +class NotificationCommandRepositoryTest extends RepositoryTestConfig { + + @Autowired + private NotificationCommandRepository notificationCommandRepository; + + @DisplayName("알림을 삭제할 경우 hard delete 가 아닌 soft delete 로 진행한다.") + @Test + void soft_delete() { + // given + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(runner); + + final Notification notification = persistNotification(runner.getMember(), notificationReferencedId(runnerPost.getId())); + + // when + notificationCommandRepository.deleteById(notification.getId()); + + // then + final Notification foundNotification = (Notification) em.createNativeQuery("select * from notification where id = :id", Notification.class) + .setParameter("id", notification.getId()) + .getSingleResult(); + + assertThat(foundNotification.getDeletedAt()).isNotNull(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/service/NotificationCommandServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/service/NotificationCommandServiceTest.java new file mode 100644 index 000000000..d4dc78fb3 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/service/NotificationCommandServiceTest.java @@ -0,0 +1,99 @@ +package touch.baton.domain.notification.command.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; +import touch.baton.domain.notification.exception.NotificationBusinessException; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.exception.NotificationDomainException; +import touch.baton.fixture.domain.NotificationFixture; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static touch.baton.fixture.vo.NotificationReferencedIdFixture.notificationReferencedId; + +class NotificationCommandServiceTest extends ServiceTestConfig { + + private NotificationCommandService notificationCommandService; + + @BeforeEach + void setUp() { + notificationCommandService = new NotificationCommandService(notificationCommandRepository); + } + + @DisplayName("알림 읽은 여부를 true 로 업데이트에 성공한다.") + @Test + void success_updateNotificationIsReadByMember() { + // given + final Member targetMember = memberCommandRepository.save(MemberFixture.createHyena()); + final NotificationReferencedId notificationReferencedId = notificationReferencedId(1L); + final Notification savedNotification = notificationCommandRepository.save(NotificationFixture.create(targetMember, notificationReferencedId)); + + // when + notificationCommandService.updateNotificationIsReadTrueByMember(targetMember, savedNotification.getId()); + + // then + final Optional maybeActual = notificationCommandRepository.findById(savedNotification.getId()); + + assertSoftly(softly -> { + softly.assertThat(maybeActual).isPresent(); + final Notification actual = maybeActual.get(); + + softly.assertThat(actual.getIsRead().getValue()).isEqualTo(true); + }); + } + + @DisplayName("알림 읽은 여부를 읽음 상태로 업데이트 할 때 알림의 주인(사용자)가 아닐 경우 예외가 발생한다.") + @Test + void fail_updateNotificationIsReadByMember_when_member_isNotOwner() { + // given + final Member targetMember = memberCommandRepository.save(MemberFixture.createHyena()); + final NotificationReferencedId notificationReferencedId = notificationReferencedId(1L); + final Notification savedNotification = notificationCommandRepository.save(NotificationFixture.create(targetMember, notificationReferencedId)); + + final Member notOwner = memberCommandRepository.save(MemberFixture.createEthan()); + + // when & then + assertThatThrownBy(() -> notificationCommandService.updateNotificationIsReadTrueByMember(notOwner, savedNotification.getId())) + .isInstanceOf(NotificationDomainException.class); + } + + @DisplayName("알림 삭제를 성공한다.") + @Test + void success_deleteNotificationByMember() { + // given + final Member targetMember = memberCommandRepository.save(MemberFixture.createHyena()); + final NotificationReferencedId notificationReferencedId = notificationReferencedId(1L); + final Notification savedNotification = notificationCommandRepository.save(NotificationFixture.create(targetMember, notificationReferencedId)); + + // when + notificationCommandService.deleteNotificationByMember(targetMember, savedNotification.getId()); + + // then + final Optional actual = notificationCommandRepository.findById(savedNotification.getId()); + + assertThat(actual).isEmpty(); + } + + @DisplayName("알림 삭제을 삭제할 때 알림의 주인(사용자)가 아닐 경우 예외가 발생한다.") + @Test + void fail_deleteNotificationByMember_when_member_isNotOwner() { + // given + final Member targetMember = memberCommandRepository.save(MemberFixture.createHyena()); + final NotificationReferencedId notificationReferencedId = notificationReferencedId(1L); + final Notification savedNotification = notificationCommandRepository.save(NotificationFixture.create(targetMember, notificationReferencedId)); + + final Member notOwner = memberCommandRepository.save(MemberFixture.createEthan()); + + // when & then + assertThatThrownBy(() -> notificationCommandService.deleteNotificationByMember(notOwner, savedNotification.getId())) + .isInstanceOf(NotificationBusinessException.class); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/IsReadTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/IsReadTest.java new file mode 100644 index 000000000..57b2d2f19 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/IsReadTest.java @@ -0,0 +1,44 @@ +package touch.baton.domain.notification.command.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +class IsReadTest { + + @DisplayName("정적 팩터리 메서드로 읽음 여부 false 생성에 성공한다") + @Test + void success_create_isRead_false() { + assertThatCode(() -> IsRead.asUnRead()) + .doesNotThrowAnyException(); + } + + @DisplayName("정적 팩터리 메서드로 읽음 여부 false 를 생성 할 수 있다.") + @Test + void success_isRead_false() { + // given + final IsRead actual = IsRead.asUnRead(); + + // when & then + assertThat(actual.getValue()).isFalse(); + } + + @DisplayName("정적 팩터리 메서드로 읽음 여부 true 생성에 성공한다") + @Test + void success_create_isRead_true() { + assertThatCode(() -> IsRead.asRead()) + .doesNotThrowAnyException(); + } + + @DisplayName("정적 팩터리 메서드로 읽음 여부 true 를 생성 할 수 있다.") + @Test + void success_isRead_true() { + // given + final IsRead actual = IsRead.asRead(); + + // when & then + assertThat(actual.getValue()).isTrue(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationMessageTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationMessageTest.java new file mode 100644 index 000000000..4496c6989 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationMessageTest.java @@ -0,0 +1,16 @@ +package touch.baton.domain.notification.command.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class NotificationMessageTest { + + @DisplayName("value 가 null 이면 예외가 발생한다.") + @Test + void fail_if_value_is_null() { + assertThatThrownBy(() -> new NotificationMessage(null)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationReferencedIdTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationReferencedIdTest.java new file mode 100644 index 000000000..b79f2cf2a --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationReferencedIdTest.java @@ -0,0 +1,16 @@ +package touch.baton.domain.notification.command.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class NotificationReferencedIdTest { + + @DisplayName("value 가 null 이면 예외가 발생한다.") + @Test + void fail_if_value_is_null() { + assertThatThrownBy(() -> new NotificationReferencedId(null)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationTitleTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationTitleTest.java new file mode 100644 index 000000000..2a175f9b6 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/command/vo/NotificationTitleTest.java @@ -0,0 +1,16 @@ +package touch.baton.domain.notification.command.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class NotificationTitleTest { + + @DisplayName("value 가 null 이면 예외가 발생한다.") + @Test + void fail_if_value_is_null() { + assertThatThrownBy(() -> new NotificationTitle(null)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepositoryTest.java new file mode 100644 index 000000000..22d09f7c5 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/query/repository/NotificationQuerydslRepositoryTest.java @@ -0,0 +1,71 @@ +package touch.baton.domain.notification.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.Notification; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static touch.baton.fixture.vo.NotificationReferencedIdFixture.notificationReferencedId; + +class NotificationQuerydslRepositoryTest extends RepositoryTestConfig { + + @Autowired + private NotificationQuerydslRepository notificationQuerydslRepository; + + @DisplayName("deleted_at 이 null 인 경우의 알림을 조회하기 위해서는 nativeQuery 를 사용한다.") + @Test + void success_findAll_deletedAt_isNull() { + // given + final Member targetMember = persistRunner(MemberFixture.createHyena()).getMember(); + + final Notification deletedAtIsNotNullNotification = persistNotification(targetMember, notificationReferencedId(1L)); + final Notification deletedAtIsNullNotification = persistNotification(targetMember, notificationReferencedId(2L)); + + // when + em.remove(deletedAtIsNullNotification); + final List actual = em.createNativeQuery("select * from notification", Notification.class).getResultList(); + + + // then + assertThat(actual).containsExactly(deletedAtIsNotNullNotification, deletedAtIsNullNotification); + } + + @DisplayName("deleted_at 이 null 이 아닌 경우 알림 목록을 사용자 식별자값을 이용해서 알림 식별자값 기준으로 내림차순 정렬하여 알림 목록을 조회한다") + @Test + void findByMemberIdOrderByIdDescLimit() { + // given + final Member targetMember = persistRunner(MemberFixture.createHyena()).getMember(); + + final List savedNotifications = new ArrayList<>(); + for (long referencedId = 1; referencedId <= 20; referencedId++) { + final Notification savedNotification = persistNotification(targetMember, notificationReferencedId(referencedId)); + savedNotifications.add(savedNotification); + } + savedNotifications.sort(orderByNotificationIdDesc()); + + // when + final int limit = 10; + final List actual = notificationQuerydslRepository.findByMemberId(targetMember.getId(), limit); + + // then + final List expected = savedNotifications.subList(0, limit); + + assertSoftly(softly -> { + softly.assertThat(actual).isSortedAccordingTo(orderByNotificationIdDesc()); + softly.assertThat(actual).containsExactlyElementsOf(expected); + }); + } + + private Comparator orderByNotificationIdDesc() { + return (left, right) -> left.getId() < right.getId() ? 1 : -1; + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/notification/query/service/NotificationQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/notification/query/service/NotificationQueryServiceTest.java new file mode 100644 index 000000000..1a0bbf726 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/notification/query/service/NotificationQueryServiceTest.java @@ -0,0 +1,77 @@ +package touch.baton.domain.notification.query.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.member.command.Member; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static touch.baton.fixture.vo.NotificationReferencedIdFixture.notificationReferencedId; + +class NotificationQueryServiceTest extends ServiceTestConfig { + + private NotificationQueryService notificationQueryService; + + @BeforeEach + void setUp() { + notificationQueryService = new NotificationQueryService(notificationQuerydslRepository); + } + + @DisplayName("사용자 식별자값으로 알림 목록을 조회에 성공한다.") + @Test + void success_readNotificationsByMemberId() { + // given + final Member targetMember = memberCommandRepository.save(MemberFixture.createHyena()); + + + final List savedNotifications = new ArrayList<>(); + for (long referencedId = 1; referencedId <= 20; referencedId++) { + final Notification savedNotification = persistNotification(targetMember, notificationReferencedId(referencedId)); + savedNotifications.add(savedNotification); + } + savedNotifications.sort(orderByNotificationIdDesc()); + + // when + final int limit = 10; + final List actual = notificationQueryService.readNotificationsByMemberId(targetMember.getId(), limit); + + // then + final List expected = savedNotifications.subList(0, limit); + + assertSoftly(softly -> { + softly.assertThat(actual).isSortedAccordingTo(orderByNotificationIdDesc()); + softly.assertThat(actual).containsExactlyElementsOf(expected); + }); + } + + private Comparator orderByNotificationIdDesc() { + return (left, right) -> left.getId() < right.getId() ? 1 : -1; + } + + @DisplayName("사용자 식별자값으로 조회한 알림 목록이 비어있을 경우 빈 목록 반환한다.") + @Test + void success_readNotificationsByMemberId_when_Notifications_isEmpty() { + // given + final Member targetMember = memberCommandRepository.save(MemberFixture.createHyena()); + + // when + final int limit = 10; + final List actual = notificationQueryService.readNotificationsByMemberId(targetMember.getId(), limit); + + // then + final List expected = Collections.emptyList(); + + assertSoftly(softly -> { + softly.assertThat(actual).isSortedAccordingTo(orderByNotificationIdDesc()); + softly.assertThat(actual).containsExactlyElementsOf(expected); + }); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/controller/OauthTypeConverterTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/controller/OauthTypeConverterTest.java similarity index 88% rename from backend/baton/src/test/java/touch/baton/domain/oauth/controller/OauthTypeConverterTest.java rename to backend/baton/src/test/java/touch/baton/domain/oauth/command/controller/OauthTypeConverterTest.java index 96a52b625..ea52ea4c1 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/controller/OauthTypeConverterTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/controller/OauthTypeConverterTest.java @@ -1,10 +1,10 @@ -package touch.baton.domain.oauth.controller; +package touch.baton.domain.oauth.command.controller; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import touch.baton.config.converter.OauthTypeConverter; -import touch.baton.domain.oauth.OauthType; +import touch.baton.domain.oauth.command.OauthType; import static org.assertj.core.api.Assertions.assertThat; diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/repository/RefreshTokenRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepositoryTest.java similarity index 62% rename from backend/baton/src/test/java/touch/baton/domain/oauth/repository/RefreshTokenRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepositoryTest.java index b4de24330..b66d7e744 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/repository/RefreshTokenRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepositoryTest.java @@ -1,13 +1,13 @@ -package touch.baton.domain.oauth.repository; +package touch.baton.domain.oauth.command.repository; import jakarta.persistence.EntityManager; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RefreshTokenFixture; @@ -15,15 +15,16 @@ import java.util.Optional; import static java.time.LocalDateTime.now; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static touch.baton.fixture.vo.ExpireDateFixture.expireDate; import static touch.baton.fixture.vo.TokenFixture.token; import static touch.baton.util.TestDateFormatUtil.createExpireDate; -class RefreshTokenRepositoryTest extends RepositoryTestConfig { +class RefreshTokenCommandRepositoryTest extends RepositoryTestConfig { @Autowired - private RefreshTokenRepository refreshTokenRepository; + private RefreshTokenCommandRepository refreshTokenCommandRepository; @Autowired private EntityManager em; @@ -32,10 +33,8 @@ class RefreshTokenRepositoryTest extends RepositoryTestConfig { @Test void findByToken() { // given - final Member ethan = MemberFixture.createEthan(); - final Member ditoo = MemberFixture.createDitoo(); - em.persist(ethan); - em.persist(ditoo); + final Member ethan = persistMember(MemberFixture.createEthan()); + final Member ditoo = persistMember(MemberFixture.createDitoo()); final LocalDateTime expireDate = createExpireDate(now().plusDays(30)); @@ -50,24 +49,22 @@ void findByToken() { em.clear(); // when - final Optional actual = refreshTokenRepository.findByToken(ethanToken); + final Optional actual = refreshTokenCommandRepository.findByToken(ethanToken); // then assertSoftly(softly -> { softly.assertThat(actual).isPresent(); softly.assertThat(actual.get().getToken()).isEqualTo(expected.getToken()); softly.assertThat(actual.get().getExpireDate()).isEqualTo(expected.getExpireDate()); - } ); + }); } @DisplayName("리프레시 토큰을 사용자로 찾을 수 있다.") @Test void findByMember() { // given - final Member owner = MemberFixture.createEthan(); - final Member notOwner = MemberFixture.createDitoo(); - em.persist(owner); - em.persist(notOwner); + final Member owner = persistMember(MemberFixture.createEthan()); + final Member notOwner = persistMember(MemberFixture.createDitoo()); final LocalDateTime expireDate = createExpireDate(now().plusDays(30)); @@ -80,13 +77,34 @@ void findByMember() { em.clear(); // when - final Optional actual = refreshTokenRepository.findByMember(owner); + final Optional actual = refreshTokenCommandRepository.findByMember(owner); // then assertSoftly(softly -> { softly.assertThat(actual).isPresent(); softly.assertThat(actual.get().getToken()).isEqualTo(expected.getToken()); softly.assertThat(actual.get().getExpireDate()).isEqualTo(expected.getExpireDate()); - } ); + }); + } + + @DisplayName("사용자를 이용해 리프레시 토큰을 삭제할 수 있다.") + @Test + void logout() { + // given + final Member owner = persistMember(MemberFixture.createEthan()); + + final LocalDateTime expireDate = createExpireDate(now().plusDays(30)); + + final RefreshToken expected = RefreshTokenFixture.create(owner, token("ethan RefreshToken"), expireDate(expireDate)); + em.persist(expected); + + em.flush(); + em.clear(); + + // when + refreshTokenCommandRepository.deleteByMember(owner); + + // then + assertThat(refreshTokenCommandRepository.findByMember(owner)).isNotPresent(); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java new file mode 100644 index 000000000..7d5ebf654 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java @@ -0,0 +1,72 @@ +package touch.baton.domain.oauth.command.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.authcode.AuthCodeRequestUrlProviderComposite; +import touch.baton.domain.oauth.command.client.OauthInformationClientComposite; +import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; +import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.infra.auth.jwt.JwtDecoder; +import touch.baton.infra.auth.jwt.JwtEncoder; + +import static org.mockito.BDDMockito.verify; +import static org.mockito.Mockito.only; + +@ExtendWith(MockitoExtension.class) +class OauthCommandServiceDeleteTest { + + private OauthCommandService oauthCommandService; + + @Mock + private AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite; + + @Mock + private OauthInformationClientComposite oauthInformationClientComposite; + + @Mock + private OauthMemberCommandRepository oauthMemberCommandRepository; + + @Mock + private OauthRunnerCommandRepository oauthRunnerCommandRepository; + + @Mock + private OauthSupporterCommandRepository oauthSupporterCommandRepository; + + @Mock + private RefreshTokenCommandRepository refreshTokenCommandRepository; + + @Mock + private JwtEncoder jwtEncoder; + + @Mock + private JwtEncoder expiredJwtEncoder; + + @Mock + private JwtDecoder jwtDecoder; + + @BeforeEach + void setUp() { + oauthCommandService = new OauthCommandService(authCodeRequestUrlProviderComposite, oauthInformationClientComposite, oauthMemberCommandRepository, oauthRunnerCommandRepository, oauthSupporterCommandRepository, refreshTokenCommandRepository, jwtEncoder, jwtDecoder); + } + + @DisplayName("Member 로 RefreshToken 을 삭제할 수 있다.") + @Test + void success_logout() { + // given + final Member ethan = MemberFixture.createEthan(); + + // when + oauthCommandService.logout(ethan); + + // then + verify(refreshTokenCommandRepository, only()).deleteByMember(ethan); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/service/OauthServiceUpdateTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceUpdateTest.java similarity index 62% rename from backend/baton/src/test/java/touch/baton/domain/oauth/service/OauthServiceUpdateTest.java rename to backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceUpdateTest.java index f13ce375a..cdcb99b34 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/service/OauthServiceUpdateTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceUpdateTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.oauth.service; +package touch.baton.domain.oauth.command.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -6,20 +6,20 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.AuthorizationHeader; -import touch.baton.domain.oauth.authcode.AuthCodeRequestUrlProviderComposite; -import touch.baton.domain.oauth.client.OauthInformationClientComposite; -import touch.baton.domain.oauth.exception.OauthRequestException; -import touch.baton.domain.oauth.repository.OauthMemberRepository; -import touch.baton.domain.oauth.repository.OauthRunnerRepository; -import touch.baton.domain.oauth.repository.OauthSupporterRepository; -import touch.baton.domain.oauth.repository.RefreshTokenRepository; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; -import touch.baton.domain.oauth.token.Tokens; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.AuthorizationHeader; +import touch.baton.domain.oauth.command.authcode.AuthCodeRequestUrlProviderComposite; +import touch.baton.domain.oauth.command.client.OauthInformationClientComposite; +import touch.baton.domain.oauth.command.exception.OauthRequestException; +import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; +import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; +import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.fixture.domain.MemberFixture; import touch.baton.infra.auth.jwt.JwtConfig; import touch.baton.infra.auth.jwt.JwtDecoder; @@ -36,23 +36,32 @@ import static touch.baton.fixture.vo.AuthorizationHeaderFixture.bearerAuthorizationHeader; @ExtendWith(MockitoExtension.class) -class OauthServiceUpdateTest { +class OauthCommandServiceUpdateTest { + + private OauthCommandService oauthCommandService; - private OauthService oauthService; @Mock private AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite; + @Mock private OauthInformationClientComposite oauthInformationClientComposite; + @Mock - private OauthMemberRepository oauthMemberRepository; + private OauthMemberCommandRepository oauthMemberCommandRepository; + @Mock - private OauthRunnerRepository oauthRunnerRepository; + private OauthRunnerCommandRepository oauthRunnerCommandRepository; + @Mock - private OauthSupporterRepository oauthSupporterRepository; + private OauthSupporterCommandRepository oauthSupporterCommandRepository; + @Mock - private RefreshTokenRepository refreshTokenRepository; + private RefreshTokenCommandRepository refreshTokenCommandRepository; + private JwtEncoder jwtEncoder; + private JwtEncoder expiredJwtEncoder; + private JwtDecoder jwtDecoder; @BeforeEach @@ -61,7 +70,7 @@ void setUp() { jwtDecoder = new JwtDecoder(normalJwtConfig); jwtEncoder = new JwtEncoder(normalJwtConfig); - oauthService = new OauthService(authCodeRequestUrlProviderComposite, oauthInformationClientComposite, oauthMemberRepository, oauthRunnerRepository, oauthSupporterRepository, refreshTokenRepository, jwtEncoder, jwtDecoder); + oauthCommandService = new OauthCommandService(authCodeRequestUrlProviderComposite, oauthInformationClientComposite, oauthMemberCommandRepository, oauthRunnerCommandRepository, oauthSupporterCommandRepository, refreshTokenCommandRepository, jwtEncoder, jwtDecoder); final JwtConfig expiredJwtConfig = new JwtConfig("secret-key-secret-key-secret-key-secret-key-secret-key-secret-key", "test-issuer", -1); expiredJwtEncoder = new JwtEncoder(expiredJwtConfig); @@ -80,11 +89,11 @@ void success_reissueAccessToken() { .expireDate(new ExpireDate(LocalDateTime.now().plusDays(30))) .build(); - given(oauthMemberRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); - given(refreshTokenRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); + given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); + given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); // when - final Tokens tokens = oauthService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue); + final Tokens tokens = oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue); // then assertSoftly(softly -> { @@ -109,7 +118,7 @@ void fail_reissueAccessToken_when_jwt_is_not_expired() { .build(); // when, then - assertThatThrownBy(() -> oauthService.reissueAccessToken(normalJwtToken, refreshTokenValue)).isInstanceOf(OauthRequestException.class); + assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(normalJwtToken, refreshTokenValue)).isInstanceOf(OauthRequestException.class); } @DisplayName("없는 socialId 가 재발급 요청하면 오류가 발생한다.") @@ -120,10 +129,10 @@ void fail_reissueAccessToken_when_socialId_not_exists() { final AuthorizationHeader expiredAuthorizationHeader = bearerAuthorizationHeader(expiredJwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); final String refreshTokenValue = "ethan-refresh-token"; - given(oauthMemberRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.empty()); + given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.empty()); // when, then - assertThatThrownBy(() -> oauthService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); + assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); } @DisplayName("refreshToken 이 없으면 재발급 요청하면 오류가 발생한다.") @@ -134,11 +143,11 @@ void fail_reissueAccessToken_when_refreshToken_not_exists() { final AuthorizationHeader expiredAuthorizationHeader = bearerAuthorizationHeader(expiredJwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); final String refreshTokenValue = "ethan-refresh-token"; - given(oauthMemberRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); - given(refreshTokenRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.empty()); + given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); + given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.empty()); // when, then - assertThatThrownBy(() -> oauthService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); + assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); } @DisplayName("주인이 아닌 accessToken 으로 재발급 요청하면 오류가 발생한다.") @@ -155,11 +164,11 @@ void fail_reissueAccessToken_when_not_owner_of_accessToken() { .build(); final Member notTokenOwner = MemberFixture.createHyena(); - given(oauthMemberRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(notTokenOwner)); - given(refreshTokenRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); + given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(notTokenOwner)); + given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); // when, then - assertThatThrownBy(() -> oauthService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); + assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); } @DisplayName("만료된 refreshToken 으로 재발급 요청하면 오류가 발생한다.") @@ -175,10 +184,10 @@ void fail_reissueAccessToken_when_refreshToken_is_expired() { .expireDate(new ExpireDate(LocalDateTime.now().minusDays(14))) .build(); - given(oauthMemberRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); - given(refreshTokenRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); + given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); + given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); // when, then - assertThatThrownBy(() -> oauthService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); + assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/token/RefreshTokenTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/token/RefreshTokenTest.java similarity index 88% rename from backend/baton/src/test/java/touch/baton/domain/oauth/token/RefreshTokenTest.java rename to backend/baton/src/test/java/touch/baton/domain/oauth/command/token/RefreshTokenTest.java index 0d3e7c5ba..3ff96740e 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/token/RefreshTokenTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/token/RefreshTokenTest.java @@ -1,26 +1,24 @@ -package touch.baton.domain.oauth.token; +package touch.baton.domain.oauth.command.token; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.oauth.token.exception.RefreshTokenDomainException; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.token.exception.RefreshTokenDomainException; import java.time.LocalDateTime; import static java.time.LocalDateTime.now; import static java.time.temporal.ChronoUnit.MINUTES; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertAll; -import static touch.baton.domain.oauth.token.RefreshToken.builder; +import static touch.baton.domain.oauth.command.token.RefreshToken.builder; class RefreshTokenTest { diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/vo/AuthorizationHeaderTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/vo/AuthorizationHeaderTest.java index 36271b0b8..7f465ca03 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/vo/AuthorizationHeaderTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/vo/AuthorizationHeaderTest.java @@ -3,11 +3,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import touch.baton.domain.oauth.AuthorizationHeader; +import touch.baton.domain.oauth.command.AuthorizationHeader; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.SoftAssertions.assertSoftly; class AuthorizationHeaderTest { diff --git a/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java b/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java index 8143ab4e5..358f7f8fd 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runner/RunnerTest.java @@ -3,15 +3,16 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.exception.RunnerDomainException; -import touch.baton.domain.technicaltag.RunnerTechnicalTags; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.member.exception.RunnerDomainException; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTags; import java.util.ArrayList; diff --git a/backend/baton/src/test/java/touch/baton/domain/runner/repository/RunnerRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/runner/repository/RunnerQueryRepositoryTest.java similarity index 71% rename from backend/baton/src/test/java/touch/baton/domain/runner/repository/RunnerRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/domain/runner/repository/RunnerQueryRepositoryTest.java index 0d67369b5..a116519cd 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runner/repository/RunnerRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runner/repository/RunnerQueryRepositoryTest.java @@ -5,15 +5,16 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.repository.MemberCommandRepository; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.member.query.repository.RunnerQueryRepository; import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; import java.util.ArrayList; @@ -22,7 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -class RunnerRepositoryTest extends RepositoryTestConfig { +class RunnerQueryRepositoryTest extends RepositoryTestConfig { private static final MemberName memberName = new MemberName("헤에디주"); private static final SocialId socialId = new SocialId("testSocialId"); @@ -32,10 +33,10 @@ class RunnerRepositoryTest extends RepositoryTestConfig { private static final ImageUrl imageUrl = new ImageUrl("김석호"); @Autowired - private RunnerRepository runnerRepository; + private RunnerQueryRepository runnerQueryRepository; @Autowired - private MemberRepository memberRepository; + private MemberCommandRepository memberCommandRepository; private Runner runner; @@ -49,7 +50,7 @@ void setUp() { .company(company) .imageUrl(imageUrl) .build(); - memberRepository.save(member); + memberCommandRepository.save(member); runner = Runner.builder() .member(member) @@ -61,10 +62,10 @@ void setUp() { @Test void findByIdJoinMember() { // given - final Runner expected = runnerRepository.save(runner); + final Runner expected = runnerQueryRepository.save(runner); // when - final Optional actual = runnerRepository.joinMemberByRunnerId(expected.getId()); + final Optional actual = runnerQueryRepository.joinMemberByRunnerId(expected.getId()); // then assertThat(actual).isPresent(); @@ -83,7 +84,7 @@ void findByIdJoinMember() { @Test void findByIdJoinMember_if_id_is_not_exists() { // when - final Optional actual = runnerRepository.joinMemberByRunnerId(999L); + final Optional actual = runnerQueryRepository.joinMemberByRunnerId(999L); // then assertThat(actual).isEmpty(); diff --git a/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerServiceUpdateTest.java b/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerCommandServiceTest.java similarity index 65% rename from backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerServiceUpdateTest.java rename to backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerCommandServiceTest.java index b9e0f4b26..02a120d5c 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerServiceUpdateTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerCommandServiceTest.java @@ -5,9 +5,10 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.service.dto.RunnerUpdateRequest; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.service.RunnerCommandService; +import touch.baton.domain.member.command.service.dto.RunnerUpdateRequest; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; @@ -17,25 +18,25 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; -class RunnerServiceUpdateTest extends ServiceTestConfig { +class RunnerCommandServiceTest extends ServiceTestConfig { - private RunnerService runnerService; + private RunnerCommandService runnerCommandService; @BeforeEach void setUp() { - runnerService = new RunnerService(runnerRepository, runnerTechnicalTagRepository, technicalTagRepository); + runnerCommandService = new RunnerCommandService(runnerTechnicalTagCommandRepository, technicalTagQueryRepository); } @DisplayName("Runner 의 프로필을 수정한다.") @Test void updateRunnerProfile() { // given - final Member memberJudy = memberRepository.save(MemberFixture.createJudy()); - final Runner runnerJudy = runnerRepository.save(RunnerFixture.createRunner(memberJudy)); + final Member memberJudy = memberCommandRepository.save(MemberFixture.createJudy()); + final Runner runnerJudy = runnerQueryRepository.save(RunnerFixture.createRunner(memberJudy)); final RunnerUpdateRequest runnerUpdateRequest = new RunnerUpdateRequest("변경된 이름", "변경된 회사", "변경된 자기소개", List.of("changedTag1", "changedTag2")); // when, then - assertThatCode(() -> runnerService.updateRunner(runnerJudy, runnerUpdateRequest)) + assertThatCode(() -> runnerCommandService.updateRunner(runnerJudy, runnerUpdateRequest)) .doesNotThrowAnyException(); } @@ -43,12 +44,12 @@ void updateRunnerProfile() { @Test void readUpdatedRunnerProfile() { // given - final Member memberJudy = memberRepository.save(MemberFixture.createJudy()); - final Runner runnerJudy = runnerRepository.save(RunnerFixture.createRunner(memberJudy)); + final Member memberJudy = memberCommandRepository.save(MemberFixture.createJudy()); + final Runner runnerJudy = runnerQueryRepository.save(RunnerFixture.createRunner(memberJudy)); final RunnerUpdateRequest runnerUpdateRequest = new RunnerUpdateRequest("변경된 이름", "변경된 회사", "변경된 자기소개", List.of("changedTag1", "changedTag2")); - runnerService.updateRunner(runnerJudy, runnerUpdateRequest); - final Runner foundRunnerJudy = runnerRepository.findById(runnerJudy.getId()).get(); + runnerCommandService.updateRunner(runnerJudy, runnerUpdateRequest); + final Runner foundRunnerJudy = runnerQueryRepository.findById(runnerJudy.getId()).get(); // when, then assertAll( diff --git a/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerServiceReadTest.java b/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerQueryServiceTest.java similarity index 67% rename from backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerServiceReadTest.java rename to backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerQueryServiceTest.java index 8feb962a6..1afe441dd 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerServiceReadTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runner/service/RunnerQueryServiceTest.java @@ -4,9 +4,10 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.query.service.RunnerQueryService; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.TechnicalTagFixture; @@ -16,28 +17,28 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -class RunnerServiceReadTest extends ServiceTestConfig { +class RunnerQueryServiceTest extends ServiceTestConfig { - private RunnerService runnerService; + private RunnerQueryService runnerQueryService; @BeforeEach void setUp() { - runnerService = new RunnerService(runnerRepository, runnerTechnicalTagRepository, technicalTagRepository); + runnerQueryService = new RunnerQueryService(runnerQueryRepository); } @DisplayName("러너를 사용자와 함께 조회한다.") @Test void readRunnerWithMember() { // given - final Member expectedMember = memberRepository.save(MemberFixture.createEthan()); + final Member expectedMember = memberCommandRepository.save(MemberFixture.createEthan()); - final TechnicalTag javaTag = technicalTagRepository.save(TechnicalTagFixture.createJava()); - final TechnicalTag reactTag = technicalTagRepository.save(TechnicalTagFixture.createReact()); + final TechnicalTag javaTag = technicalTagQueryRepository.save(TechnicalTagFixture.createJava()); + final TechnicalTag reactTag = technicalTagQueryRepository.save(TechnicalTagFixture.createReact()); final List technicalTags = List.of(javaTag, reactTag); - final Runner expectedRunner = runnerRepository.save(RunnerFixture.createRunner(expectedMember, technicalTags)); + final Runner expectedRunner = runnerQueryRepository.save(RunnerFixture.createRunner(expectedMember, technicalTags)); // when - final Runner actualRunner = runnerService.readByRunnerId(expectedRunner.getId()); + final Runner actualRunner = runnerQueryService.readByRunnerId(expectedRunner.getId()); // then final Member actualMember = actualRunner.getMember(); diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java index b49968c4b..f92dbdb91 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java @@ -6,30 +6,31 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.exception.RunnerPostDomainException; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.vo.ReviewCount; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.RunnerPostTags; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.exception.RunnerPostDomainException; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.command.vo.Title; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.RunnerPostTags; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; import java.time.LocalDateTime; @@ -39,7 +40,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.junit.jupiter.api.Assertions.assertAll; @@ -98,7 +101,7 @@ void addAllRunnerPostTags() { // when runnerPost.addAllRunnerPostTags(List.of(java, spring)); - List runnerPostTags = runnerPost.getRunnerPostTags().getRunnerPostTags(); + final List runnerPostTags = runnerPost.getRunnerPostTags().getRunnerPostTags(); final List actualTagNames = runnerPostTags.stream() .map(runnerPostTag -> runnerPostTag.getTag().getTagName().getValue()) .collect(Collectors.toList()); @@ -479,7 +482,7 @@ void fail_NOT_STARTED__to_IN_PROGRESS() { .runnerPostTags(new RunnerPostTags(new ArrayList<>())) .build(); - // when & then + // when, then assertThatThrownBy(() -> runnerPost.updateReviewStatus(ReviewStatus.IN_PROGRESS)) .isInstanceOf(RunnerPostDomainException.class); } @@ -503,7 +506,7 @@ void fail_NOT_STARTED__to_DONE() { .runnerPostTags(new RunnerPostTags(new ArrayList<>())) .build(); - // when & then + // when, then assertThatThrownBy(() -> runnerPost.updateReviewStatus(ReviewStatus.DONE)) .isInstanceOf(RunnerPostDomainException.class); } @@ -527,7 +530,7 @@ void fail_DONE_to_NOT_STARTED() { .runnerPostTags(new RunnerPostTags(new ArrayList<>())) .build(); - // when & then + // when, then assertThatThrownBy(() -> runnerPost.updateReviewStatus(ReviewStatus.NOT_STARTED)) .isInstanceOf(RunnerPostDomainException.class); } @@ -551,7 +554,7 @@ void fail_DONE_to_IN_PROGRESS() { .runnerPostTags(new RunnerPostTags(new ArrayList<>())) .build(); - // when & then + // when, then assertThatThrownBy(() -> runnerPost.updateReviewStatus(ReviewStatus.IN_PROGRESS)) .isInstanceOf(RunnerPostDomainException.class); } @@ -576,7 +579,7 @@ void fail_same_to_same(final ReviewStatus reviewStatus) { .runnerPostTags(new RunnerPostTags(new ArrayList<>())) .build(); - // when & then + // when, then assertThatThrownBy(() -> runnerPost.updateReviewStatus(reviewStatus)) .isInstanceOf(RunnerPostDomainException.class); } diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostsApplicantCountTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostsApplicantCountTest.java new file mode 100644 index 000000000..3bb3a3242 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostsApplicantCountTest.java @@ -0,0 +1,74 @@ +package touch.baton.domain.runnerpost; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.domain.runnerpost.command.RunnerPostsApplicantCount; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.repository.dto.RunnerPostApplicantCountDto; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class RunnerPostsApplicantCountTest { + + @DisplayName("게시글 지원자 수 DTO 목록으로 생성한다.") + @Test + void createByRunnerPostApplicantCountDto() { + // given + final List dtos = new ArrayList<>(); + final long applicantCount = 3L; + for (long runnerPostId = 1; runnerPostId <= 10; runnerPostId++) { + dtos.add(new RunnerPostApplicantCountDto(runnerPostId, applicantCount)); + } + + // when, then + assertThatCode(() -> RunnerPostsApplicantCount.from(dtos)) + .doesNotThrowAnyException(); + } + + @DisplayName("게시글 지원자 수 DTO 목록이 null 이면 예외가 발생한다.") + @Test + void createByRunnerPostApplicantCountDto_fail_when_dto_is_null() { + // given + final List dtos = null; + + // when, then + assertThatThrownBy(() -> RunnerPostsApplicantCount.from(dtos)) + .isInstanceOf(RunnerPostBusinessException.class); + } + + @DisplayName("러너 게시글 지원자 수를 조회한다.") + @Test + void readRunnerPostsApplicantCount() { + // given + final List dtos = new ArrayList<>(); + final Long runnerPostId = 1L; + final Long applicantCount = 3L; + dtos.add(new RunnerPostApplicantCountDto(runnerPostId, applicantCount)); + final RunnerPostsApplicantCount runnerPostsApplicantCount = RunnerPostsApplicantCount.from(dtos); + + // when + final Long actual = runnerPostsApplicantCount.getApplicantCountById(runnerPostId); + + // then + assertThat(actual).isEqualTo(applicantCount); + } + + @DisplayName("러너 게시글 지원자 수를 조회할 때 없는 runnerPostId 로 조회하면 예외가 발생한다.") + @Test + void readRunnerPostsApplicantCount_fail_if_id_not_exist() { + // given + final List dtos = new ArrayList<>(); + final Long runnerPostId = 1L; + final long applicantCount = 3L; + dtos.add(new RunnerPostApplicantCountDto(runnerPostId, applicantCount)); + final RunnerPostsApplicantCount runnerPostsApplicantCount = RunnerPostsApplicantCount.from(dtos); + + // when, then + final Long invalidRunnerPostId = 0L; + assertThatThrownBy(() -> runnerPostsApplicantCount.getApplicantCountById(invalidRunnerPostId)) + .isInstanceOf(RunnerPostBusinessException.class); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/exception/validator/UrlValidatorTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/exception/validator/UrlValidatorTest.java similarity index 98% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/exception/validator/UrlValidatorTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/exception/validator/UrlValidatorTest.java index 4fc38051c..83b550f6f 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/exception/validator/UrlValidatorTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/exception/validator/UrlValidatorTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.exception.validator; +package touch.baton.domain.runnerpost.command.exception.validator; import jakarta.validation.ClockProvider; import jakarta.validation.ConstraintValidatorContext; diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryDeleteTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/repository/RunnerPostRepositoryDeleteTest.java similarity index 53% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryDeleteTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/repository/RunnerPostRepositoryDeleteTest.java index 7c953fefa..14380fbfb 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryDeleteTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/repository/RunnerPostRepositoryDeleteTest.java @@ -1,30 +1,31 @@ -package touch.baton.domain.runnerpost.repository; +package touch.baton.domain.runnerpost.command.repository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.tag.RunnerPostTags; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.repository.MemberCommandRepository; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.member.query.repository.RunnerQueryRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.command.vo.Title; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; +import touch.baton.domain.runnerpost.query.repository.RunnerPostQueryRepository; +import touch.baton.domain.tag.command.RunnerPostTags; import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; import java.time.LocalDateTime; @@ -36,13 +37,13 @@ class RunnerPostRepositoryDeleteTest extends RepositoryTestConfig { @Autowired - private MemberRepository memberRepository; + private MemberCommandRepository memberCommandRepository; @Autowired - private RunnerRepository runnerRepository; + private RunnerQueryRepository runnerQueryRepository; @Autowired - private RunnerPostRepository runnerPostRepository; + private RunnerPostQueryRepository runnerPostQueryRepository; @DisplayName("RunnerPost 식별자값으로 RunnerPost 을 삭제한다.") @Test @@ -56,13 +57,13 @@ void success_deleteByRunnerPostId() { .company(new Company("우아한형제들")) .imageUrl(new ImageUrl("홍혁준")) .build(); - final Member saveMember = memberRepository.saveAndFlush(member); + final Member saveMember = memberCommandRepository.saveAndFlush(member); final Runner runner = Runner.builder() .member(saveMember) .runnerTechnicalTags(RunnerTechnicalTagsFixture.create(new ArrayList<>())) .build(); - final Runner saveRunner = runnerRepository.saveAndFlush(runner); + final Runner saveRunner = runnerQueryRepository.saveAndFlush(runner); final RunnerPost runnerPost = RunnerPost.builder() .title(new Title("제 코드 리뷰 좀 해주세요!!")) @@ -78,12 +79,12 @@ void success_deleteByRunnerPostId() { .runner(saveRunner) .supporter(null) .build(); - final Long saveRunnerPostId = runnerPostRepository.saveAndFlush(runnerPost).getId(); + final Long saveRunnerPostId = runnerPostQueryRepository.saveAndFlush(runnerPost).getId(); // when - runnerPostRepository.deleteById(saveRunnerPostId); + runnerPostQueryRepository.deleteById(saveRunnerPostId); - final Optional maybeRunnerPost = runnerPostRepository.findById(saveRunnerPostId); + final Optional maybeRunnerPost = runnerPostQueryRepository.findById(saveRunnerPostId); // then assertThat(maybeRunnerPost).isNotPresent(); diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceCreateTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceCreateTest.java similarity index 54% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceCreateTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceCreateTest.java index f054bb57c..e069c2963 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceCreateTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceCreateTest.java @@ -1,24 +1,24 @@ -package touch.baton.domain.runnerpost.service; +package touch.baton.domain.runnerpost.command.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.exception.RunnerPostBusinessException; -import touch.baton.domain.runnerpost.service.dto.RunnerPostApplicantCreateRequest; -import touch.baton.domain.runnerpost.service.dto.RunnerPostCreateRequest; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostApplicantCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.Title; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -35,7 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static touch.baton.fixture.vo.DeadlineFixture.deadline; -class RunnerPostServiceCreateTest extends ServiceTestConfig { +class RunnerPostCommandServiceCreateTest extends ServiceTestConfig { private static final String TITLE = "코드 리뷰 해주세요."; private static final String TAG = "Java"; @@ -46,16 +46,16 @@ class RunnerPostServiceCreateTest extends ServiceTestConfig { private static final String CURIOUS_CONTENTS = "궁금해."; private static final String POSTSCRIPT_CONTENTS = "싸게 부탁드려요."; - private RunnerPostService runnerPostService; + private RunnerPostCommandService runnerPostCommandService; @BeforeEach void setUp() { - runnerPostService = new RunnerPostService( - runnerPostRepository, - runnerPostTagRepository, - tagRepository, - supporterRepository, - supporterRunnerPostRepository + runnerPostCommandService = new RunnerPostCommandService( + runnerPostCommandRepository, + tagCommandRepository, + supporterCommandRepository, + supporterRunnerPostCommandRepository, + publisher ); } @@ -70,15 +70,15 @@ void success() { IMPLEMENTED_CONTENTS, CURIOUS_CONTENTS, POSTSCRIPT_CONTENTS); - final Member ethanMember = memberRepository.save(MemberFixture.createEthan()); - final Runner runner = runnerRepository.save(RunnerFixture.createRunner(ethanMember)); + final Member ethanMember = memberCommandRepository.save(MemberFixture.createEthan()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(ethanMember)); // when - final Long savedId = runnerPostService.createRunnerPost(runner, request); + final Long savedId = runnerPostCommandService.createRunnerPost(runner, request); // then assertThat(savedId).isNotNull(); - final Optional maybeActual = runnerPostRepository.findById(savedId); + final Optional maybeActual = runnerPostQueryRepository.findById(savedId); assertThat(maybeActual).isPresent(); final RunnerPost actual = maybeActual.get(); assertAll( @@ -97,19 +97,19 @@ void success() { @Test void success_createRunnerPostApplicant() { // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitto = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitto = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterQueryRepository.save(SupporterFixture.create(savedMemberHyena)); - final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitto, deadline(now().plusHours(100)))); + final RunnerPost savedRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create(savedRunnerDitto, deadline(now().plusHours(100)))); // when final RunnerPostApplicantCreateRequest request = new RunnerPostApplicantCreateRequest("안녕하세요. 서포터 헤나입니다."); - final Long savedRunnerPostApplicantId = runnerPostService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId()); + final Long savedRunnerPostApplicantId = runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId()); - final Optional maybeRunnerPostApplicant = supporterRunnerPostRepository.findById(savedRunnerPostApplicantId); + final Optional maybeRunnerPostApplicant = supporterRunnerPostQueryRepository.findById(savedRunnerPostApplicantId); // then assertSoftly(softly -> { @@ -127,17 +127,17 @@ void success_createRunnerPostApplicant() { @Test void fail_createRunnerPostApplicant_if_runnerPost_is_null() { // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitto = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitto = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterQueryRepository.save(SupporterFixture.create(savedMemberHyena)); // when final RunnerPostApplicantCreateRequest request = new RunnerPostApplicantCreateRequest("안녕하세요. 서포터 헤나입니다."); // then - assertThatThrownBy(() -> runnerPostService.createRunnerPostApplicant(savedSupporterHyena, request, 0L)) + assertThatThrownBy(() -> runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, request, 0L)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -145,22 +145,22 @@ void fail_createRunnerPostApplicant_if_runnerPost_is_null() { @Test void fail_createRunnerPostApplicant_if_supporter_already_applied() { // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitto = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitto = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterQueryRepository.save(SupporterFixture.create(savedMemberHyena)); - final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitto, deadline(now().plusHours(100)))); + final RunnerPost savedRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create(savedRunnerDitto, deadline(now().plusHours(100)))); // when final RunnerPostApplicantCreateRequest request = new RunnerPostApplicantCreateRequest("안녕하세요. 서포터 헤나입니다."); // then assertSoftly(softly -> { - softly.assertThatCode(() -> runnerPostService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId())) + softly.assertThatCode(() -> runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId())) .doesNotThrowAnyException(); - softly.assertThatThrownBy(() -> runnerPostService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId())) + softly.assertThatThrownBy(() -> runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId())) .isInstanceOf(RunnerPostBusinessException.class); }); } diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java new file mode 100644 index 000000000..ea25d3e5b --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java @@ -0,0 +1,119 @@ +package touch.baton.domain.runnerpost.command.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.SupporterFixture; +import touch.baton.fixture.domain.SupporterRunnerPostFixture; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; + +class RunnerPostCommandServiceDeleteTest extends ServiceTestConfig { + + private RunnerPostCommandService runnerPostCommandService; + + @BeforeEach + void setUp() { + runnerPostCommandService = new RunnerPostCommandService( + runnerPostCommandRepository, + tagCommandRepository, + supporterCommandRepository, + supporterRunnerPostCommandRepository, + publisher + ); + } + + @DisplayName("RunnerPost 식별자값으로 RunnerPost 을 삭제한다.") + @Test + void success_deleteByRunnerPostId() { + // given + final Member member = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + final RunnerPost runnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(10)))); + + // when + runnerPostCommandService.deleteByRunnerPostId(runnerPost.getId(), runner); + + // then + assertThat(runnerPostQueryRepository.existsById(runnerPost.getId())).isFalse(); + } + + @DisplayName("RunnerPost 식별자값으로 존재하지 않는 RunnerPost 를 삭제 시도할 경우 예외가 발생한다.") + @Test + void fail_deleteByRunnerPostId_if_runnerPost_is_null() { + final Member member = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + assertThatThrownBy(() -> runnerPostCommandService.deleteByRunnerPostId(0L, runner)) + .isInstanceOf(RunnerPostBusinessException.class); + } + + @DisplayName("RunnerPost 를 작성하지 않은 Runner 가 삭제 시도할 경우 예외가 발생한다.") + @Test + void fail_deleteByRunnerPostId_if_not_owner() { + // given + final Member memberRunnerPostOwner = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runnerPostOwner = runnerQueryRepository.save(RunnerFixture.createRunner(memberRunnerPostOwner)); + final RunnerPost runnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + runnerPostOwner, + deadline(LocalDateTime.now().plusHours(10)) + )); + final Member memberRunnerPostNotOwner = memberCommandRepository.save(MemberFixture.createJudy()); + final Runner runnerPostNotOwner = runnerQueryRepository.save(RunnerFixture.createRunner(memberRunnerPostNotOwner)); + + // when, then + assertThatThrownBy(() -> runnerPostCommandService.deleteByRunnerPostId(runnerPost.getId(), runnerPostNotOwner)) + .isInstanceOf(RunnerPostBusinessException.class); + } + + @DisplayName("NOT_STARTED 상태가 아닌 RunnerPost 를 삭제 시도할 경우 예외가 발생한다.") + @Test + void fail_deleteByRunnerPostId_if_reviewStatus_is_not_NOT_STARTED() { + // given + final Member memberRunner = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(memberRunner)); + final RunnerPost runnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + runner, + deadline(LocalDateTime.now().plusHours(10)) + )); + final Member memberSupporter = memberCommandRepository.save(MemberFixture.createEthan()); + final Supporter supporter = supporterQueryRepository.save(SupporterFixture.create(memberSupporter)); + supporterRunnerPostQueryRepository.save(SupporterRunnerPostFixture.create(runnerPost, supporter)); + runnerPost.assignSupporter(supporter); + + // when, then + assertThatThrownBy(() -> runnerPostCommandService.deleteByRunnerPostId(runnerPost.getId(), runner)) + .isInstanceOf(RunnerPostBusinessException.class); + } + + @DisplayName("지원한 서포터가 있는 경우에 RunnerPost 를 삭제 시도할 경우 예외가 발생한다.") + @Test + void fail_deleteByRunnerPostId_if_applicant_is_exist() { + // given + final Member member = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + final RunnerPost runnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + runner, + deadline(LocalDateTime.now().plusHours(10)) + )); + final Member memberSupporter = memberCommandRepository.save(MemberFixture.createEthan()); + final Supporter supporter = supporterQueryRepository.save(SupporterFixture.create(memberSupporter)); + supporterRunnerPostQueryRepository.save(SupporterRunnerPostFixture.create(runnerPost, supporter)); + + // when, then + assertThatThrownBy(() -> runnerPostCommandService.deleteByRunnerPostId(runnerPost.getId(), runner)) + .isInstanceOf(RunnerPostBusinessException.class); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java new file mode 100644 index 000000000..00042c60b --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java @@ -0,0 +1,119 @@ +package touch.baton.domain.runnerpost.command.service; + +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.test.context.event.ApplicationEvents; +import org.springframework.test.context.event.RecordApplicationEvents; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.event.RunnerPostApplySupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostAssignSupporterEvent; +import touch.baton.domain.runnerpost.command.event.RunnerPostReviewStatusDoneEvent; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostApplicantCreateRequest; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.SupporterFixture; + +import static java.time.LocalDateTime.now; +import static org.assertj.core.api.Assertions.assertThat; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; + +@RecordApplicationEvents +class RunnerPostCommandServiceEventTest extends ServiceTestConfig { + + @Autowired + private ApplicationEvents applicationEvents; + + private RunnerPostCommandService runnerPostCommandService; + + @BeforeEach + void setUp() { + runnerPostCommandService = new RunnerPostCommandService( + runnerPostCommandRepository, + tagCommandRepository, + supporterCommandRepository, + supporterRunnerPostCommandRepository, + publisher + ); + } + + @DisplayName("서포터가 러너 게시글에 리뷰를 지원하면 이벤트가 발행된다.") + @Test + void success_supporter_apply_runnerPost() { + // given + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitto = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterCommandRepository.save(SupporterFixture.create(savedMemberHyena)); + + final RunnerPost savedRunnerPost = runnerPostCommandRepository.save(RunnerPostFixture.create(savedRunnerDitto, deadline(now().plusHours(100)))); + + // when + final RunnerPostApplicantCreateRequest request = new RunnerPostApplicantCreateRequest("안녕하세요. 서포터 헤나입니다."); + runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, request, savedRunnerPost.getId()); + + // then + final long eventPublishedCount = applicationEvents.stream(RunnerPostApplySupporterEvent.class).count(); + + assertThat(eventPublishedCount).isOne(); + } + + @DisplayName("러너는 자신의 러너 게시글의 지원자 중 한 명을 서포터로서 확정하면 이벤트가 발행된다.") + @Test + void success_runner_assign_applicant_supporter() { + // given + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitto = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterCommandRepository.save(SupporterFixture.create(savedMemberHyena)); + + final RunnerPost savedRunnerPost = runnerPostCommandRepository.save(RunnerPostFixture.create(savedRunnerDitto, deadline(now().plusHours(100)))); + + final RunnerPostApplicantCreateRequest runnerPostApplicantCreateRequest = new RunnerPostApplicantCreateRequest("안녕하세요. 서포터 헤나입니다."); + runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, runnerPostApplicantCreateRequest, savedRunnerPost.getId()); + + // when + final RunnerPostUpdateRequest.SelectSupporter runnerPostAssignSupporterRequest = new RunnerPostUpdateRequest.SelectSupporter(savedSupporterHyena.getId()); + runnerPostCommandService.updateRunnerPostAppliedSupporter(savedRunnerDitto, savedRunnerPost.getId(), runnerPostAssignSupporterRequest); + + // then + final long eventPublishedCount = applicationEvents.stream(RunnerPostAssignSupporterEvent.class).count(); + + assertThat(eventPublishedCount).isOne(); + } + + @DisplayName("서포터가 러너 게시글의 상태를 리뷰 완료로 변경할 경우 이벤트가 발행된다.") + @Test + void success_supporter_update_runnerPost_reviewStatus_done() { + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitto = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterCommandRepository.save(SupporterFixture.create(savedMemberHyena)); + + final RunnerPost savedRunnerPost = runnerPostCommandRepository.save(RunnerPostFixture.create(savedRunnerDitto, deadline(now().plusHours(100)))); + + final RunnerPostApplicantCreateRequest runnerPostApplicantCreateRequest = new RunnerPostApplicantCreateRequest("안녕하세요. 서포터 헤나입니다."); + runnerPostCommandService.createRunnerPostApplicant(savedSupporterHyena, runnerPostApplicantCreateRequest, savedRunnerPost.getId()); + + final RunnerPostUpdateRequest.SelectSupporter runnerPostAssignSupporterRequest = new RunnerPostUpdateRequest.SelectSupporter(savedSupporterHyena.getId()); + runnerPostCommandService.updateRunnerPostAppliedSupporter(savedRunnerDitto, savedRunnerPost.getId(), runnerPostAssignSupporterRequest); + + // when + runnerPostCommandService.updateRunnerPostReviewStatusDone(savedRunnerPost.getId(), savedSupporterHyena); + + // then + final long eventPublishedCount = applicationEvents.stream(RunnerPostReviewStatusDoneEvent.class).count(); + + assertThat(eventPublishedCount).isOne(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceUpdateTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceUpdateTest.java similarity index 52% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceUpdateTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceUpdateTest.java index 76ec6e56f..4beaca789 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceUpdateTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceUpdateTest.java @@ -1,18 +1,18 @@ -package touch.baton.domain.runnerpost.service; +package touch.baton.domain.runnerpost.command.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.exception.RunnerPostBusinessException; -import touch.baton.domain.runnerpost.exception.RunnerPostDomainException; -import touch.baton.domain.runnerpost.service.dto.RunnerPostUpdateRequest; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.exception.RunnerPostDomainException; +import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -25,11 +25,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.IN_PROGRESS; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.OVERDUE; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.IN_PROGRESS; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.OVERDUE; import static touch.baton.fixture.vo.DeadlineFixture.deadline; -class RunnerPostServiceUpdateTest extends ServiceTestConfig { +class RunnerPostCommandServiceUpdateTest extends ServiceTestConfig { private static Runner runnerPostOwner; private static RunnerPost targetRunnerPost; @@ -37,32 +37,32 @@ class RunnerPostServiceUpdateTest extends ServiceTestConfig { private static Runner runner; private static Supporter assignedSupporter; - private RunnerPostService runnerPostService; + private RunnerPostCommandService runnerPostCommandService; @BeforeEach void setUp() { - runnerPostService = new RunnerPostService( - runnerPostRepository, - runnerPostTagRepository, - tagRepository, - supporterRepository, - supporterRunnerPostRepository + runnerPostCommandService = new RunnerPostCommandService( + runnerPostCommandRepository, + tagCommandRepository, + supporterCommandRepository, + supporterRunnerPostCommandRepository, + publisher ); - final Member ehtanMember = memberRepository.save(MemberFixture.createEthan()); - runnerPostOwner = runnerRepository.save(RunnerFixture.createRunner(ehtanMember)); - targetRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(runnerPostOwner, + final Member ehtanMember = memberCommandRepository.save(MemberFixture.createEthan()); + runnerPostOwner = runnerQueryRepository.save(RunnerFixture.createRunner(ehtanMember)); + targetRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create(runnerPostOwner, deadline(LocalDateTime.now().plusDays(10)))); - final Member hyenaMember = memberRepository.save(MemberFixture.createHyena()); - applySupporter = supporterRepository.save(SupporterFixture.create(hyenaMember)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(targetRunnerPost, applySupporter)); + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + applySupporter = supporterQueryRepository.save(SupporterFixture.create(hyenaMember)); + supporterRunnerPostQueryRepository.save(SupporterRunnerPostFixture.create(targetRunnerPost, applySupporter)); - final Member runnerMember = memberRepository.save(MemberFixture.createEthan()); - runner = runnerRepository.save(RunnerFixture.createRunner(runnerMember)); + final Member runnerMember = memberCommandRepository.save(MemberFixture.createEthan()); + runner = runnerQueryRepository.save(RunnerFixture.createRunner(runnerMember)); - final Member supporterMember = memberRepository.save(MemberFixture.createDitoo()); - assignedSupporter = supporterRepository.save(SupporterFixture.create(supporterMember)); + final Member supporterMember = memberCommandRepository.save(MemberFixture.createDitoo()); + assignedSupporter = supporterQueryRepository.save(SupporterFixture.create(supporterMember)); } @DisplayName("러너는 자신의 글에 제안한 서포터를 서포터로 선택할 수 있다.") @@ -72,10 +72,10 @@ void updateRunnerPostAppliedSupporter() { final RunnerPostUpdateRequest.SelectSupporter request = new RunnerPostUpdateRequest.SelectSupporter(applySupporter.getId()); // when - runnerPostService.updateRunnerPostAppliedSupporter(runnerPostOwner, targetRunnerPost.getId(), request); + runnerPostCommandService.updateRunnerPostAppliedSupporter(runnerPostOwner, targetRunnerPost.getId(), request); // then - final Optional maybeRunnerPost = runnerPostRepository.findById(targetRunnerPost.getId()); + final Optional maybeRunnerPost = runnerPostQueryRepository.findById(targetRunnerPost.getId()); assertThat(maybeRunnerPost).isPresent(); final RunnerPost actualRunnerPost = maybeRunnerPost.get(); @@ -93,7 +93,7 @@ void fail_updateRunnerPostAppliedSupporter_if_not_join_supporter() { final RunnerPostUpdateRequest.SelectSupporter request = new RunnerPostUpdateRequest.SelectSupporter(notJoinSupporterId); // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostAppliedSupporter(runnerPostOwner, targetRunnerPost.getId(), request)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostAppliedSupporter(runnerPostOwner, targetRunnerPost.getId(), request)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -101,13 +101,13 @@ void fail_updateRunnerPostAppliedSupporter_if_not_join_supporter() { @Test void fail_updateRunnerPostAppliedSupporter_if_not_apply_supporter() { // given - final Member ditooMember = memberRepository.save(MemberFixture.createDitoo()); - final Supporter notApplySupporter = supporterRepository.save(SupporterFixture.create(ditooMember)); + final Member ditooMember = memberCommandRepository.save(MemberFixture.createDitoo()); + final Supporter notApplySupporter = supporterQueryRepository.save(SupporterFixture.create(ditooMember)); final RunnerPostUpdateRequest.SelectSupporter request = new RunnerPostUpdateRequest.SelectSupporter(notApplySupporter.getId()); // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostAppliedSupporter(runnerPostOwner, targetRunnerPost.getId(), request)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostAppliedSupporter(runnerPostOwner, targetRunnerPost.getId(), request)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -119,7 +119,7 @@ void fail_updateRunnerPostAppliedSupporter_if_is_not_written_runnerPost() { final Long notWrittenRunnerPostId = 1000000L; // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostAppliedSupporter(runnerPostOwner, notWrittenRunnerPostId, request)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostAppliedSupporter(runnerPostOwner, notWrittenRunnerPostId, request)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -127,13 +127,13 @@ void fail_updateRunnerPostAppliedSupporter_if_is_not_written_runnerPost() { @Test void fail_updateRunnerPostAppliedSupporter_if_is_not_owner_of_runnerPost() { // given - final Member ditooMember = memberRepository.save(MemberFixture.createDitoo()); - final Runner notOwnerRunner = runnerRepository.save(RunnerFixture.createRunner(ditooMember)); + final Member ditooMember = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner notOwnerRunner = runnerQueryRepository.save(RunnerFixture.createRunner(ditooMember)); final RunnerPostUpdateRequest.SelectSupporter request = new RunnerPostUpdateRequest.SelectSupporter(applySupporter.getId()); // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostAppliedSupporter(notOwnerRunner, targetRunnerPost.getId(), request)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostAppliedSupporter(notOwnerRunner, targetRunnerPost.getId(), request)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -142,13 +142,13 @@ void fail_updateRunnerPostAppliedSupporter_if_is_not_owner_of_runnerPost() { void updateRunnerPostReviewStatusDone() { // given final IsReviewed isReviewed = IsReviewed.notReviewed(); - final RunnerPost targetRunnerPost = runnerPostRepository.save(RunnerPostFixture.createWithReviewStatus(runner, assignedSupporter, IN_PROGRESS, isReviewed)); + final RunnerPost targetRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.createWithSupporter(runner, assignedSupporter, IN_PROGRESS, isReviewed)); // when - runnerPostService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), assignedSupporter); + runnerPostCommandService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), assignedSupporter); // then - final Optional maybeRunnerPost = runnerPostRepository.findById(targetRunnerPost.getId()); + final Optional maybeRunnerPost = runnerPostQueryRepository.findById(targetRunnerPost.getId()); assertThat(maybeRunnerPost).isPresent(); final RunnerPost actualRunnerPost = maybeRunnerPost.get(); assertThat(actualRunnerPost.getReviewStatus()).isEqualTo(ReviewStatus.DONE); @@ -159,11 +159,11 @@ void updateRunnerPostReviewStatusDone() { void fail_updateRunnerPostReviewStatusDone_if_invalid_runnerPostId() { // given final IsReviewed isReviewed = IsReviewed.notReviewed(); - runnerPostRepository.save(RunnerPostFixture.createWithReviewStatus(runner, assignedSupporter, IN_PROGRESS, isReviewed)); + runnerPostQueryRepository.save(RunnerPostFixture.createWithSupporter(runner, assignedSupporter, IN_PROGRESS, isReviewed)); final Long unsavedRunnerPostId = 100000L; // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostReviewStatusDone(unsavedRunnerPostId, assignedSupporter)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostReviewStatusDone(unsavedRunnerPostId, assignedSupporter)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -172,10 +172,10 @@ void fail_updateRunnerPostReviewStatusDone_if_invalid_runnerPostId() { void fail_updateRunnerPostReviewStatusDone_if_supporter_is_null() { // given final IsReviewed isReviewed = IsReviewed.notReviewed(); - final RunnerPost targetRunnerPost = runnerPostRepository.save(RunnerPostFixture.createWithReviewStatus(runner, null, IN_PROGRESS, isReviewed)); + final RunnerPost targetRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.createWithSupporter(runner, null, IN_PROGRESS, isReviewed)); // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), assignedSupporter)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), assignedSupporter)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -184,12 +184,12 @@ void fail_updateRunnerPostReviewStatusDone_if_supporter_is_null() { void fail_updateRunnerPostReviewStatusDone_if_different_supporter_is_assigned() { // given final IsReviewed isReviewed = IsReviewed.notReviewed(); - final RunnerPost targetRunnerPost = runnerPostRepository.save(RunnerPostFixture.createWithReviewStatus(runner, assignedSupporter, IN_PROGRESS, isReviewed)); - final Member differentMember = memberRepository.save(MemberFixture.createHyena()); - final Supporter differentSupporter = supporterRepository.save(SupporterFixture.create(differentMember)); + final RunnerPost targetRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.createWithSupporter(runner, assignedSupporter, IN_PROGRESS, isReviewed)); + final Member differentMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter differentSupporter = supporterQueryRepository.save(SupporterFixture.create(differentMember)); // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), differentSupporter)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), differentSupporter)) .isInstanceOf(RunnerPostBusinessException.class); } @@ -198,10 +198,10 @@ void fail_updateRunnerPostReviewStatusDone_if_different_supporter_is_assigned() void fail_updateRunnerPostReviewStatusDone_if_reviewStatus_is_overdue() { // given final IsReviewed isReviewed = IsReviewed.notReviewed(); - final RunnerPost targetRunnerPost = runnerPostRepository.save(RunnerPostFixture.createWithReviewStatus(runner, assignedSupporter, OVERDUE, isReviewed)); + final RunnerPost targetRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.createWithSupporter(runner, assignedSupporter, OVERDUE, isReviewed)); // when, then - assertThatThrownBy(() -> runnerPostService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), assignedSupporter)) + assertThatThrownBy(() -> runnerPostCommandService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), assignedSupporter)) .isInstanceOf(RunnerPostDomainException.class); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostUpdateApplicantCancelationServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostUpdateApplicantCancelationServiceTest.java similarity index 51% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostUpdateApplicantCancelationServiceTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostUpdateApplicantCancelationServiceTest.java index ee3afe856..4d50c0ec3 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostUpdateApplicantCancelationServiceTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostUpdateApplicantCancelationServiceTest.java @@ -1,18 +1,18 @@ -package touch.baton.domain.runnerpost.service; +package touch.baton.domain.runnerpost.command.service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.exception.RunnerPostBusinessException; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -24,60 +24,66 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -public class RunnerPostUpdateApplicantCancelationServiceTest extends ServiceTestConfig { +class RunnerPostUpdateApplicantCancelationServiceTest extends ServiceTestConfig { - private RunnerPostService runnerPostService; + private RunnerPostCommandService runnerPostCommandService; private Supporter applicantSupporter; private Runner revieweeRunner; @BeforeEach void setUp() { - runnerPostService = new RunnerPostService(runnerPostRepository, runnerPostTagRepository, tagRepository, supporterRepository, supporterRunnerPostRepository); - - final Member applicantMember = memberRepository.save(MemberFixture.createDitoo()); - applicantSupporter = supporterRepository.save(SupporterFixture.create(applicantMember)); - - final Member revieweeMember = memberRepository.save(MemberFixture.createJudy()); - revieweeRunner = runnerRepository.save(RunnerFixture.createRunner(revieweeMember)); + runnerPostCommandService = new RunnerPostCommandService( + runnerPostCommandRepository, + tagCommandRepository, + supporterCommandRepository, + supporterRunnerPostCommandRepository, + publisher + ); + + final Member applicantMember = memberCommandRepository.save(MemberFixture.createDitoo()); + applicantSupporter = supporterQueryRepository.save(SupporterFixture.create(applicantMember)); + + final Member revieweeMember = memberCommandRepository.save(MemberFixture.createJudy()); + revieweeRunner = runnerQueryRepository.save(RunnerFixture.createRunner(revieweeMember)); } @DisplayName("성공한다.") @Test void success() { // given - final RunnerPost runnerPost = runnerPostRepository.save( + final RunnerPost runnerPost = runnerPostQueryRepository.save( RunnerPostFixture.create( revieweeRunner, applicantSupporter, new Deadline(LocalDateTime.now().plusHours(100)) )); final SupporterRunnerPost supporterRunnerPost = SupporterRunnerPostFixture.create(runnerPost, applicantSupporter); - supporterRunnerPostRepository.save(supporterRunnerPost); + supporterRunnerPostQueryRepository.save(supporterRunnerPost); // when - runnerPostService.deleteSupporterRunnerPost(applicantSupporter, runnerPost.getId()); + runnerPostCommandService.deleteSupporterRunnerPost(applicantSupporter, runnerPost.getId()); // then - assertThat(supporterRunnerPostRepository.findById(supporterRunnerPost.getId())).isNotPresent(); + assertThat(supporterRunnerPostQueryRepository.findById(supporterRunnerPost.getId())).isNotPresent(); } @DisplayName("RunnerPost 가 존재하지 않으면 실패한다.") @Test void fail_when_runnerPost_not_found() { // given - final RunnerPost runnerPost = runnerPostRepository.save( + final RunnerPost runnerPost = runnerPostQueryRepository.save( RunnerPostFixture.create( revieweeRunner, applicantSupporter, new Deadline(LocalDateTime.now().plusHours(100)) )); final SupporterRunnerPost supporterRunnerPost = SupporterRunnerPostFixture.create(runnerPost, applicantSupporter); - supporterRunnerPostRepository.save(supporterRunnerPost); - runnerPostRepository.delete(runnerPost); + supporterRunnerPostQueryRepository.save(supporterRunnerPost); + runnerPostQueryRepository.delete(runnerPost); - // when & then - assertThatThrownBy(() -> runnerPostService.deleteSupporterRunnerPost(applicantSupporter, runnerPost.getId())) + // when, then + assertThatThrownBy(() -> runnerPostCommandService.deleteSupporterRunnerPost(applicantSupporter, runnerPost.getId())) .isInstanceOf(RunnerPostBusinessException.class); } @@ -86,7 +92,7 @@ void fail_when_runnerPost_not_found() { void fail_when_runnerPost_reviewStatus_is_not_NOT_STARTED() { // given final IsReviewed isReviewed = IsReviewed.notReviewed(); - final RunnerPost runnerPost = runnerPostRepository.save( + final RunnerPost runnerPost = runnerPostQueryRepository.save( RunnerPostFixture.create( revieweeRunner, applicantSupporter, @@ -95,11 +101,11 @@ void fail_when_runnerPost_reviewStatus_is_not_NOT_STARTED() { isReviewed )); final SupporterRunnerPost supporterRunnerPost = SupporterRunnerPostFixture.create(runnerPost, applicantSupporter); - supporterRunnerPostRepository.save(supporterRunnerPost); + supporterRunnerPostQueryRepository.save(supporterRunnerPost); - // when & then - assertThatThrownBy(() -> runnerPostService.deleteSupporterRunnerPost(applicantSupporter, runnerPost.getId())) + // when, then + assertThatThrownBy(() -> runnerPostCommandService.deleteSupporterRunnerPost(applicantSupporter, runnerPost.getId())) .isInstanceOf(RunnerPostBusinessException.class); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/CuriousContentsTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/CuriousContentsTest.java similarity index 89% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/CuriousContentsTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/CuriousContentsTest.java index d86174940..a3330f476 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/CuriousContentsTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/CuriousContentsTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/DeadlineTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/DeadlineTest.java similarity index 88% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/DeadlineTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/DeadlineTest.java index 644b60bbb..cea8dc170 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/DeadlineTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/DeadlineTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/ImplementedContentsTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/ImplementedContentsTest.java similarity index 89% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/ImplementedContentsTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/ImplementedContentsTest.java index b53e21ad0..c7ac255a5 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/ImplementedContentsTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/ImplementedContentsTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/IsReviewedTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/IsReviewedTest.java similarity index 90% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/IsReviewedTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/IsReviewedTest.java index fb3183b21..697e42705 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/IsReviewedTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/IsReviewedTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/PostscriptContentsTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/PostscriptContentsTest.java similarity index 89% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/PostscriptContentsTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/PostscriptContentsTest.java index 216de4e35..265886439 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/PostscriptContentsTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/PostscriptContentsTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/PullRequestUrlTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/PullRequestUrlTest.java similarity index 89% rename from backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/PullRequestUrlTest.java rename to backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/PullRequestUrlTest.java index 86f583eff..21cb8d768 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/vo/PullRequestUrlTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/vo/PullRequestUrlTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.runnerpost.vo; +package touch.baton.domain.runnerpost.command.vo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java new file mode 100644 index 000000000..c1c1a589c --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java @@ -0,0 +1,501 @@ +package touch.baton.domain.runnerpost.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerPostFixture; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.*; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; + +class RunnerPostPageRepositoryTest extends RepositoryTestConfig { + + @Autowired + private RunnerPostPageRepository runnerPostPageRepository; + + @DisplayName("러너 게시글을 페이징 조회한다 (중간 페이지 조회)") + @Test + void findByPageInfo() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final ReviewStatus[] allReviewStatus = ReviewStatus.values(); + final int persistSize = 30; + final List runnerPostIds = new ArrayList<>(); + for (int i = 0; i < persistSize; i++) { + runnerPostIds.add(persistRunnerPost(runner, allReviewStatus[i % allReviewStatus.length]).getId()); + } + final int lastIndex = persistSize - 1; + final Long previousLastId = runnerPostIds.get(lastIndex); + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, null, null); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.subList(1, 1 + limit); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("러너 게시글을 페이징 조회한다 (첫 페이지 조회)") + @Test + void findLatestByLimit() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final ReviewStatus[] allReviewStatus = ReviewStatus.values(); + final int runnerPostCount = 10; + final List runnerPostIds = new ArrayList<>(); + for (int i = 0; i < runnerPostCount; i++) { + runnerPostIds.add(persistRunnerPost(runner, allReviewStatus[i % allReviewStatus.length]).getId()); + } + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, null, null); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds; + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("리뷰 상태로 러너 게시글을 페이징 조회한다 (중간 페이지 조회)") + @Test + void findByPageInfoAndReviewStatus() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final ReviewStatus reviewStatus = NOT_STARTED; + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + runnerPostIds.add(persistRunnerPost(runner, reviewStatus).getId()); + } + final int lastIndex = persistSize - 1; + final Long previousLastId = runnerPostIds.get(lastIndex); + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, null, reviewStatus); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.subList(1, 1 + limit); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("리뷰 상태로 러너 게시글을 페이징 조회한다 (첫 페이지 조회)") + @Test + void findLatestByLimitAndReviewStatus() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final ReviewStatus reviewStatus = NOT_STARTED; + final int runnerPostCount = 10; + final List runnerPostIds = new ArrayList<>(); + for (int i = 0; i < runnerPostCount; i++) { + runnerPostIds.add(persistRunnerPost(runner, reviewStatus).getId()); + } + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, null, reviewStatus); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds; + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("축약된 태그 이름과 리뷰 상태로 러너 게시글을 페이징 조회한다 (중간 페이지 조회)") + @Test + void findByPageInfoAndReviewStatusAndTagReducedName() { + // given + final String tagName = "Javascript"; + final ReviewStatus reviewStatus = NOT_STARTED; + + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final Tag tag = persistTag(tagName); + + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, reviewStatus); + persistRunnerPostTag(runnerPost, tag); + runnerPostIds.add(runnerPost.getId()); + } + final int lastIndex = persistSize - 1; + final Long previousLastId = runnerPostIds.get(lastIndex); + final int limit = 10; + + em.flush(); + em.close(); + + // when + final TagReducedName tagReducedName = TagReducedName.from(tagName); + final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, tagReducedName, reviewStatus); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.subList(1, 1 + limit); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("축약된 태그 이름과 리뷰 상태로 러너 게시글을 페이징 조회한다 (첫 페이지 조회)") + @Test + void findLatestByLimitAndTagNameAndReviewStatus() { + // given + final String tagName = "Java"; + final ReviewStatus reviewStatus = NOT_STARTED; + + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final Tag tag = persistTag(tagName); + + final List runnerPostIds = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, reviewStatus); + persistRunnerPostTag(runnerPost, tag); + runnerPostIds.add(runnerPost.getId()); + } + final int limit = 10; + + em.flush(); + em.close(); + + // when + final TagReducedName tagReducedName = TagReducedName.from(tagName); + final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, tagReducedName, reviewStatus); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds; + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("서포터 외래키로 리뷰 상태가 NOT_STARTED 인 러너 게시글을 페이징 조회한다 (중간 페이지 조회)") + @Test + void findByPageInfoAndSupporterIdAndReviewStatus_NOT_STARTED() { + // given + final String tagName = "Javascript"; + final Tag tag = persistTag(tagName); + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, NOT_STARTED); + persistRunnerPostTag(runnerPost, tag); + if (runnerPost.getId() % 2 == 0) { + persistApplicant(supporter, runnerPost); + } + runnerPostIds.add(runnerPost.getId()); + } + final int lastIndex = persistSize - 1; + final Long previousLastId = runnerPostIds.get(lastIndex); + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageBySupporterIdAndReviewStatusNotStarted(previousLastId, limit, supporter.getId()); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.stream() + .filter(id -> id % 2 == 0 && id < previousLastId) + .limit(limit) + .toList(); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("서포터 외래키로 리뷰 상태가 NOT_STARTED 인 러너 게시글을 페이징 조회한다 (첫 페이지 조회)") + @Test + void findLatestByLimitAndSupporterIdAndReviewStatus_NOT_STARTED() { + // given + final String tagName = "Java"; + final Tag tag = persistTag(tagName); + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, NOT_STARTED); + persistRunnerPostTag(runnerPost, tag); + if (runnerPost.getId() % 2 == 0) { + persistApplicant(supporter, runnerPost); + } + runnerPostIds.add(runnerPost.getId()); + } + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageBySupporterIdAndReviewStatusNotStarted(null, limit, supporter.getId()); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.stream() + .filter(id -> id % 2 == 0) + .limit(limit) + .toList(); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("서포터 외래키로 리뷰 상태가 IN_PROGRESS 인 러너 게시글을 페이징 조회한다 (중간 페이지 조회)") + @Test + void findByPageInfoAndSupporterIdAndReviewStatus_IN_PROGRESS() { + // given + final String tagName = "Javascript"; + final Tag tag = persistTag(tagName); + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, NOT_STARTED); + persistRunnerPostTag(runnerPost, tag); + if (runnerPost.getId() % 2 == 0) { + persistApplicant(supporter, runnerPost); + runnerPost.assignSupporter(supporter); + } + runnerPostIds.add(runnerPost.getId()); + } + final int lastIndex = persistSize - 1; + final Long previousLastId = runnerPostIds.get(lastIndex); + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageBySupporterIdAndReviewStatus(previousLastId, limit, supporter.getId(), IN_PROGRESS); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.stream() + .filter(id -> id % 2 == 0 && id < previousLastId) + .limit(limit) + .toList(); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("서포터 외래키로 리뷰 상태가 DONE 인 러너 게시글을 페이징 조회한다 (첫 페이지 조회)") + @Test + void findLatestByLimitAndSupporterIdAndReviewStatus_DONE() { + // given + final String tagName = "Java"; + final Tag tag = persistTag(tagName); + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, NOT_STARTED); + persistRunnerPostTag(runnerPost, tag); + if (runnerPost.getId() % 2 == 0) { + persistApplicant(supporter, runnerPost); + runnerPost.assignSupporter(supporter); + runnerPost.finishReview(); + } + runnerPostIds.add(runnerPost.getId()); + } + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageBySupporterIdAndReviewStatus(null, limit, supporter.getId(), DONE); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.stream() + .filter(id -> id % 2 == 0) + .limit(limit) + .toList(); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("러너 외래키로 리뷰 상태가 NOT_STARTED 인 러너 게시글을 페이징 조회한다 (중간 페이지 조회)") + @Test + void findByPageInfoAndRunnerIdAndReviewStatus_NOT_STARTED() { + // given + final String tagName = "Javascript"; + final Tag tag = persistTag(tagName); + final Runner runner = persistRunner(MemberFixture.createDitoo()); + + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, NOT_STARTED); + persistRunnerPostTag(runnerPost, tag); + runnerPostIds.add(runnerPost.getId()); + + } + final int lastIndex = persistSize - 1; + final Long previousLastId = runnerPostIds.get(lastIndex); + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageByRunnerIdAndReviewStatus(previousLastId, limit, runner.getId(), NOT_STARTED); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.stream() + .filter(id -> id < previousLastId) + .limit(limit) + .toList(); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("러너 외래키로 리뷰 상태가 OVERDUE 인 러너 게시글을 페이징 조회한다 (첫 페이지 조회)") + @Test + void findLatestByLimitAndRunnerIdAndReviewStatus_OVERDUE() { + // given + final String tagName = "Java"; + final Tag tag = persistTag(tagName); + final Runner runner = persistRunner(MemberFixture.createDitoo()); + + final List runnerPostIds = new ArrayList<>(); + final int persistSize = 30; + for (int i = 0; i < persistSize; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner, OVERDUE); + persistRunnerPostTag(runnerPost, tag); + runnerPostIds.add(runnerPost.getId()); + } + final int limit = 10; + + em.flush(); + em.close(); + + // when + final List runnerPosts = runnerPostPageRepository.pageByRunnerIdAndReviewStatus(null, limit, runner.getId(), OVERDUE); + runnerPostIds.sort(Comparator.reverseOrder()); + final List expected = runnerPostIds.stream() + .limit(limit) + .toList(); + + // then + assertSoftly(softly -> { + softly.assertThat(runnerPosts).hasSize(limit); + softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + .isEqualTo(expected); + }); + } + + @DisplayName("RunnerPost 목록으로 RunnerPostTag 목록을 조회한다.") + @Test + void findRunnerPostTagsByRunnerPosts() { + // given + final Tag tagReact = persistTag("react"); + final Tag tagJava = persistTag("java"); + + final List runnerPosts = new ArrayList<>(); + final List expected = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final RunnerPostTag runnerPostTagReact = persistRunnerPostTag(runnerPost, tagReact); + final RunnerPostTag runnerPostTagJava = persistRunnerPostTag(runnerPost, tagJava); + runnerPosts.add(runnerPost); + expected.addAll(List.of(runnerPostTagReact, runnerPostTagJava)); + } + + em.flush(); + em.close(); + + // when + final List actual = runnerPostPageRepository.findRunnerPostTagsByRunnerPosts(runnerPosts); + + // then + assertThat(actual).isEqualTo(expected); + } + + private RunnerPost persistRunnerPost(final Runner runner, final ReviewStatus reviewStatus) { + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(100)), reviewStatus, IsReviewed.notReviewed()); + em.persist(runnerPost); + return runnerPost; + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryReadTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryReadTest.java new file mode 100644 index 000000000..d676f869b --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryReadTest.java @@ -0,0 +1,117 @@ +package touch.baton.domain.runnerpost.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.query.repository.RunnerPostTagQueryRepository; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.RunnerPostTagFixture; +import touch.baton.fixture.domain.TagFixture; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.NOT_STARTED; +import static touch.baton.fixture.domain.RunnerPostTagsFixture.runnerPostTags; +import static touch.baton.fixture.vo.CuriousContentsFixture.curiousContents; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; +import static touch.baton.fixture.vo.ImplementedContentsFixture.implementedContents; +import static touch.baton.fixture.vo.PostscriptContentsFixture.postscriptContents; +import static touch.baton.fixture.vo.PullRequestUrlFixture.pullRequestUrl; +import static touch.baton.fixture.vo.TitleFixture.title; +import static touch.baton.fixture.vo.WatchedCountFixture.watchedCount; + +class RunnerPostQueryRepositoryReadTest extends RepositoryTestConfig { + + @Autowired + private RunnerPostQueryRepository runnerPostQueryRepository; + + @Autowired + private RunnerPostTagQueryRepository runnerPostTagQueryRepository; + + @DisplayName("RunnerPost 식별자로 RunnerPostTag 목록을 조회할 때 Tag 가 있으면 조회된다.") + @Test + void findRunnerPostTagsById_exist() { + // given + final Member ditoo = MemberFixture.createDitoo(); + em.persist(ditoo); + final Runner runner = RunnerFixture.createRunner(ditoo); + em.persist(runner); + + final RunnerPost runnerPost = RunnerPostFixture.create(title("제 코드를 리뷰해주세요"), + implementedContents("제 코드의 내용은 이렇습니다."), + curiousContents("저는 이것이 궁금합니다."), + postscriptContents("잘 부탁드립니다."), + pullRequestUrl("https://"), + deadline(LocalDateTime.now().plusHours(10)), + watchedCount(0), + NOT_STARTED, + IsReviewed.notReviewed(), + runner, + null, + runnerPostTags(new ArrayList<>())); + runnerPostQueryRepository.save(runnerPost); + + final Tag java = TagFixture.createJava(); + em.persist(java); + final Tag spring = TagFixture.createSpring(); + em.persist(spring); + final RunnerPostTag javaRunnerPostTag = RunnerPostTagFixture.create(runnerPost, java); + final RunnerPostTag springRunnerPostTag = RunnerPostTagFixture.create(runnerPost, spring); + + runnerPost.addAllRunnerPostTags(List.of(javaRunnerPostTag, springRunnerPostTag)); + + em.flush(); + em.close(); + + // when + final List expected = runnerPostTagQueryRepository.joinTagByRunnerPostId(runnerPost.getId()); + + // then + assertThat(expected).containsExactly(javaRunnerPostTag, springRunnerPostTag); + } + + @DisplayName("Runner 식별자로 RunnerPost 목록을 조회한다.") + @Test + void findByRunnerId() { + // given + final Member ditoo = MemberFixture.createDitoo(); + em.persist(ditoo); + final Runner runner = RunnerFixture.createRunner(ditoo); + em.persist(runner); + + em.flush(); + em.close(); + + final RunnerPost runnerPost = RunnerPostFixture.create(title("제 코드를 리뷰해주세요"), + implementedContents("제 코드의 내용은 이렇습니다."), + curiousContents("저는 이것이 궁금합니다."), + postscriptContents("잘 부탁드립니다."), + pullRequestUrl("https://"), + deadline(LocalDateTime.now().plusHours(10)), + watchedCount(0), + NOT_STARTED, + IsReviewed.notReviewed(), + runner, + null, + runnerPostTags(new ArrayList<>())); + runnerPostQueryRepository.save(runnerPost); + + // when + final List actual = runnerPostQueryRepository.findByRunnerId(runner.getId()); + + // then + assertThat(actual).containsExactly(runnerPost); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryTest.java new file mode 100644 index 000000000..f645ad6ba --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostQueryRepositoryTest.java @@ -0,0 +1,197 @@ +package touch.baton.domain.runnerpost.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.repository.dto.RunnerPostApplicantCountDto; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +class RunnerPostQueryRepositoryTest extends RepositoryTestConfig { + + @Autowired + private RunnerPostQueryRepository runnerPostQueryRepository; + + @DisplayName("러너 게시글 식별자값 목록으로 서포터 지원자 수를 조회에 성공한다.") + @Test + void countApplicantsByRunnerPostIds() { + // given + final Runner hyenaRunner = persistRunner(MemberFixture.createHyena()); + final Supporter ditooSupporter = persistSupporter(MemberFixture.createDitoo()); + final Supporter ethanSupporter = persistSupporter(MemberFixture.createEthan()); + final Supporter judySupporter = persistSupporter(MemberFixture.createJudy()); + + final RunnerPost runnerPostOne = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostTwo = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostThree = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostFour = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostFive = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostSix = persistRunnerPost(hyenaRunner); + + persistApplicant(ditooSupporter, runnerPostOne); + persistApplicant(ethanSupporter, runnerPostOne); + persistApplicant(judySupporter, runnerPostOne); + + persistApplicant(ditooSupporter, runnerPostTwo); + persistApplicant(ethanSupporter, runnerPostTwo); + + persistApplicant(ditooSupporter, runnerPostThree); + + em.flush(); + em.close(); + + // when + final List actual = runnerPostQueryRepository.countApplicantsByRunnerPostIds(List.of( + runnerPostOne.getId(), + runnerPostTwo.getId(), + runnerPostThree.getId(), + runnerPostFour.getId(), + runnerPostFive.getId(), + runnerPostSix.getId(), + runnerPostOne.getId() + )); + + // then + final List expected = List.of( + new RunnerPostApplicantCountDto(runnerPostTwo.getId(), 2L), + new RunnerPostApplicantCountDto(runnerPostThree.getId(), 1L), + new RunnerPostApplicantCountDto(runnerPostFour.getId(), 0L), + new RunnerPostApplicantCountDto(runnerPostFive.getId(), 0L), + new RunnerPostApplicantCountDto(runnerPostSix.getId(), 0L), + new RunnerPostApplicantCountDto(runnerPostOne.getId(), 3L) + ); + + assertThat(actual).containsExactlyInAnyOrderElementsOf(expected); + } + + @DisplayName("러너 게시글 식별자값 목록으로 서포터 지원자 수 매핑 정보 조회에 성공한다.") + @Test + void findApplicantCountMappingByRunnerPostIds() { + // given + final Runner hyenaRunner = persistRunner(MemberFixture.createHyena()); + final Supporter ditooSupporter = persistSupporter(MemberFixture.createDitoo()); + final Supporter ethanSupporter = persistSupporter(MemberFixture.createEthan()); + final Supporter judySupporter = persistSupporter(MemberFixture.createJudy()); + + final RunnerPost runnerPostOne = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostTwo = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostThree = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostFour = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostFive = persistRunnerPost(hyenaRunner); + final RunnerPost runnerPostSix = persistRunnerPost(hyenaRunner); + + persistApplicant(ditooSupporter, runnerPostOne); + persistApplicant(ethanSupporter, runnerPostOne); + persistApplicant(judySupporter, runnerPostOne); + + persistApplicant(ditooSupporter, runnerPostTwo); + persistApplicant(ethanSupporter, runnerPostTwo); + + persistApplicant(ditooSupporter, runnerPostThree); + + em.flush(); + em.close(); + + // when + final List actual = runnerPostQueryRepository.countApplicantsByRunnerPostIds(List.of( + runnerPostOne.getId(), + runnerPostTwo.getId(), + runnerPostThree.getId(), + runnerPostFour.getId(), + runnerPostFive.getId(), + runnerPostSix.getId() + )); + + // then + final List expected = new ArrayList<>(List.of( + new RunnerPostApplicantCountDto(runnerPostOne.getId(), 3L), + new RunnerPostApplicantCountDto(runnerPostTwo.getId(), 2L), + new RunnerPostApplicantCountDto(runnerPostThree.getId(), 1L), + new RunnerPostApplicantCountDto(runnerPostFour.getId(), 0L), + new RunnerPostApplicantCountDto(runnerPostFive.getId(), 0L), + new RunnerPostApplicantCountDto(runnerPostSix.getId(), 0L) + )); + + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("러너 게시글 식별자값으로 러너 게시글과 서포터, 사용자를 조인하여 조회한다.") + @Test + void joinSupporterByRunnerPostId() { + // given + final Runner hyenaRunner = persistRunner(MemberFixture.createHyena()); + final Supporter ditooSupporter = persistSupporter(MemberFixture.createDitoo()); + final RunnerPost runnerPost = persistRunnerPost(hyenaRunner); + persistApplicant(ditooSupporter, runnerPost); + runnerPost.assignSupporter(ditooSupporter); + + em.flush(); + em.close(); + + // when + final Optional maybeActual = runnerPostQueryRepository.joinSupporterByRunnerPostId(runnerPost.getId()); + + // then + assertSoftly(softly -> { + softly.assertThat(maybeActual).isPresent(); + final RunnerPost actual = maybeActual.get(); + + softly.assertThat(actual.getSupporter()).isEqualTo(ditooSupporter); + softly.assertThat(actual).isEqualTo(runnerPost); + }); + } + + @DisplayName("러너 관련 게시글 개수를 조회하는데 성공한다.") + @Test + void countByRunnerIdAndReviewStatus() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final int expected = 5; + for (int i = 0; i < expected; i++) { + persistRunnerPost(runner); + } + + em.flush(); + em.close(); + + // when + final Long actual = runnerPostQueryRepository.countByRunnerIdAndReviewStatus(runner.getId(), ReviewStatus.NOT_STARTED); + + // then + assertThat(actual.intValue()).isEqualTo(expected); + } + + @DisplayName("서포터 관련 게시글 개수를 조회하는데 성공한다.") + @Test + void countBySupporterIdAndReviewStatus() { + // given + final Runner runner = persistRunner(MemberFixture.createEthan()); + final Supporter supporter = persistSupporter(MemberFixture.createDitoo()); + final int expected = 3; + for (int i = 0; i < expected; i++) { + final RunnerPost runnerPost = persistRunnerPost(runner); + runnerPost.assignSupporter(supporter); + runnerPost.finishReview(); + } + + em.flush(); + em.close(); + + // when + final Long actual = runnerPostQueryRepository.countBySupporterIdAndReviewStatus(supporter.getId(), ReviewStatus.DONE); + + // then + assertThat(actual.intValue()).isEqualTo(expected); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java new file mode 100644 index 000000000..be09c19ca --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java @@ -0,0 +1,859 @@ +package touch.baton.domain.runnerpost.query.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.common.request.PageParams; +import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.command.vo.Title; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; +import touch.baton.domain.runnerpost.query.controller.response.RunnerPostResponse; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.RunnerPostTags; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RunnerFixture; +import touch.baton.fixture.domain.RunnerPostFixture; +import touch.baton.fixture.domain.RunnerPostTagFixture; +import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; +import touch.baton.fixture.domain.SupporterFixture; +import touch.baton.fixture.domain.SupporterRunnerPostFixture; +import touch.baton.fixture.domain.TagFixture; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static java.time.LocalDateTime.now; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.IN_PROGRESS; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.NOT_STARTED; +import static touch.baton.fixture.vo.DeadlineFixture.deadline; +import static touch.baton.fixture.vo.TagNameFixture.tagName; + +class RunnerPostQueryServiceTest extends ServiceTestConfig { + + private RunnerPostQueryService runnerPostQueryService; + + @BeforeEach + void setUp() { + runnerPostQueryService = new RunnerPostQueryService( + runnerPostQueryRepository, + runnerPostPageRepository, + runnerPostTagQueryRepository, + supporterRunnerPostQueryRepository); + } + + @DisplayName("태그 이름과 리뷰 상태를 조건으로 러너 게시글 첫 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfoAndTagNameAndReviewStatus_firstPage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag, springTag), + NOT_STARTED + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().minusHours(100)), + List.of(javaTag), + ReviewStatus.OVERDUE + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(springTag), + NOT_STARTED + )); + + // when + final PageParams pageParams = new PageParams(null, 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + javaTag.getTagName().getValue(), + pageParams, + NOT_STARTED + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("태그 이름과 리뷰 상태를 조건으로 러너 게시글 중간 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfoAndTagNameAndReviewStatus_middlePage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag, springTag), + NOT_STARTED + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().minusHours(100)), + List.of(javaTag, springTag), + ReviewStatus.OVERDUE + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + NOT_STARTED + )); + + final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + // when + final PageParams pageParams = new PageParams(previousRunnerPost.getId(), 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + javaTag.getTagName().getValue(), + pageParams, + NOT_STARTED + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("리뷰 상태를 조건으로 러너 게시글 첫 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfoAndReviewStatus_firstPage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag, springTag), + NOT_STARTED + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + final RunnerPost expectedRunnerPostThree = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + NOT_STARTED + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().minusHours(100)), + List.of(springTag), + ReviewStatus.OVERDUE + )); + + // when + final PageParams pageParams = new PageParams(null, 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + null, + pageParams, + NOT_STARTED + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostThree, springTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("리뷰 상태를 조건으로 러너 게시글 중간 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfoAndReviewStatus_middlePage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag, springTag), + NOT_STARTED + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + final RunnerPost expectedRunnerPostThree = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + NOT_STARTED + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().minusHours(100)), + List.of(springTag), + ReviewStatus.OVERDUE + )); + + final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + NOT_STARTED + )); + + // when + final PageParams pageParams = new PageParams(previousRunnerPost.getId(), 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + null, + pageParams, + NOT_STARTED + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostThree, springTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("태그 이름을 조건으로 러너 게시글 첫 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfoAndTagName_firstPage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now()), + List.of(javaTag, springTag), + ReviewStatus.OVERDUE + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + ReviewStatus.IN_PROGRESS + )); + + // when + final PageParams pageParams = new PageParams(null, 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + javaTag.getTagName().getValue(), + pageParams, + null + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("태그 이름을 조건으로 러너 게시글 중간 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfoAndTagName_middlePage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag, springTag), + NOT_STARTED + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now()), + List.of(javaTag), + ReviewStatus.OVERDUE + )); + + runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + ReviewStatus.IN_PROGRESS + )); + + final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + // when + final PageParams pageParams = new PageParams(previousRunnerPost.getId(), 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + javaTag.getTagName().getValue(), + pageParams, + null + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("조건 없이 러너 게시글 첫 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfo_firstPage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now()), + List.of(javaTag, springTag), + ReviewStatus.OVERDUE + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + final RunnerPost expectedRunnerPostThree = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + IN_PROGRESS + )); + + // when + final PageParams pageParams = new PageParams(null, 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + null, + pageParams, + null + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostThree, springTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("조건 없이 러너 게시글 중간 페이지 조회에 성공한다.") + @Test + void readRunnerPostByPageInfo_middlePage() { + // given + final Member hyenaMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Runner hyenaRunner = runnerQueryRepository.save(RunnerFixture.createRunner(hyenaMember)); + + final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); + final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + + final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now().plusHours(100)), + List.of(javaTag, springTag), + NOT_STARTED + )); + + final RunnerPost expectedRunnerPostTwo = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(LocalDateTime.now()), + List.of(javaTag), + ReviewStatus.OVERDUE + )); + + final RunnerPost expectedRunnerPostThree = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(springTag), + IN_PROGRESS + )); + + final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + hyenaRunner, + deadline(now().plusHours(100)), + List.of(javaTag), + NOT_STARTED + )); + + // when + final PageParams pageParams = new PageParams(previousRunnerPost.getId(), 10); + final PageResponse actual = runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus( + null, + pageParams, + null + ); + + final List runnerPostTags = List.of( + RunnerPostTagFixture.create(expectedRunnerPostOne, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), + RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag), + RunnerPostTagFixture.create(expectedRunnerPostThree, springTag) + ); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), + RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("RunnerPost 식별자로 RunnerPost 를 조회한다.") + @Test + void success_findByRunnerPostId() { + // given + final Member member = Member.builder() + .memberName(new MemberName("헤에디주")) + .socialId(new SocialId("testSocialId")) + .oauthId(new OauthId("dsigjh98gh230gn2oinv913bcuo23nqovbvu93b12voi3bc31j")) + .githubUrl(new GithubUrl("github.com/hyena0608")) + .company(new Company("우아한형제들")) + .imageUrl(new ImageUrl("홍혁준")) + .build(); + memberCommandRepository.save(member); + + final Runner runner = Runner.builder() + .member(member) + .runnerTechnicalTags(RunnerTechnicalTagsFixture.create(new ArrayList<>())) + .build(); + runnerQueryRepository.save(runner); + + final LocalDateTime deadline = now(); + final RunnerPost runnerPost = RunnerPost.builder() + .title(new Title("제 코드 리뷰 좀 해주세요!!")) + .implementedContents(new ImplementedContents("제 코드는 클린코드가 맞을까요?")) + .curiousContents(new CuriousContents("궁금해요.")) + .postscriptContents(new PostscriptContents("잘 부탁드립니다.")) + .deadline(new Deadline(deadline)) + .pullRequestUrl(new PullRequestUrl("https://")) + .watchedCount(new WatchedCount(0)) + .runnerPostTags(new RunnerPostTags(new ArrayList<>())) + .reviewStatus(NOT_STARTED) + .isReviewed(IsReviewed.notReviewed()) + .runner(runner) + .supporter(null) + .build(); + runnerPostQueryRepository.save(runnerPost); + + final Tag tag = Tag.builder() + .tagName(new TagName("자바")) + .tagReducedName(TagReducedName.from("자바")) + .build(); + tagCommandRepository.save(tag); + + final RunnerPostTag runnerPostTag = RunnerPostTag.builder() + .runnerPost(runnerPost) + .tag(tag) + .build(); + runnerPost.addAllRunnerPostTags(List.of(runnerPostTag)); + + // when + final RunnerPost findRunnerPost = runnerPostQueryService.readByRunnerPostId(runnerPost.getId()); + + // then + assertThat(findRunnerPost) + .usingRecursiveComparison() + .ignoringExpectedNullFields() + .isEqualTo(runnerPost); + } + + @DisplayName("RunnerPost 식별자로 존재하지 않는 RunnerPost 를 조회할 경우 예외가 발생한다.") + @Test + void fail_findByRunnerPostId_if_runner_post_is_null() { + assertThatThrownBy(() -> runnerPostQueryService.readByRunnerPostId(0L)) + .isInstanceOf(RunnerPostBusinessException.class) + .hasMessage("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다."); + } + + @DisplayName("Runner 식별자값으로 RunnerPost 를 조회한다.") + @Test + void success_findByRunnerId() { + // given + final Member ditoo = MemberFixture.createDitoo(); + memberCommandRepository.save(ditoo); + final Runner runner = RunnerFixture.createRunner(ditoo); + runnerQueryRepository.save(runner); + final RunnerPost expected = RunnerPostFixture.create(runner, new Deadline(now().plusHours(100))); + runnerPostQueryRepository.save(expected); + + // when + final List actual = runnerPostQueryService.readRunnerPostsByRunnerId(runner.getId()); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(1); + softly.assertThat(actual.get(0)).isEqualTo(expected); + }); + } + + @DisplayName("Supporter 외래키와 ReviewStatus 로 러너 게시글을 조회한다. (NOT_STARTED 제외)") + @Test + void readRunnerPostsBySupporterIdAndReviewStatus() { + // given + final Member savedMemberEthan = memberCommandRepository.save(MemberFixture.createEthan()); + final Runner savedRunnerEthan = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberEthan)); + + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterQueryRepository.save(SupporterFixture.create(savedMemberHyena)); + + final RunnerPost runnerPost = RunnerPostFixture.create(savedRunnerEthan, deadline(now().plusHours(100))); + final RunnerPost savedRunnerPost = runnerPostQueryRepository.save(runnerPost); + savedRunnerPost.assignSupporter(savedSupporterHyena); + + supporterRunnerPostQueryRepository.save(SupporterRunnerPostFixture.create(runnerPost, savedSupporterHyena)); + + // when + final PageParams pageParams = new PageParams(null, 10); + final PageResponse actual + = runnerPostQueryService.pageRunnerPostBySupporterIdAndReviewStatus(pageParams, savedSupporterHyena.getId(), IN_PROGRESS); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(savedRunnerPost, 1L, Collections.emptyList())), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("Supporter 외래키와 ReviewStatus 로 러너 게시글을 조회한다. (NOT_STARTED 인 경우)") + @Test + void readRunnerPostsBySupporterIdAndReviewStatusIs_NOT_STARTED() { + // given + final Member member = memberCommandRepository.save(MemberFixture.createEthan()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + + final Member supporterMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter supporter = supporterQueryRepository.save(SupporterFixture.create(supporterMember)); + + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline(now().plusHours(100))); + final RunnerPost savedRunnerPost = runnerPostQueryRepository.save(runnerPost); + + supporterRunnerPostQueryRepository.save(SupporterRunnerPostFixture.create(runnerPost, supporter)); + + // when + final PageParams pageParams = new PageParams(null, 10); + final PageResponse actual + = runnerPostQueryService.pageRunnerPostBySupporterIdAndReviewStatus(pageParams, supporter.getId(), NOT_STARTED); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.Simple.of(savedRunnerPost, 1L, Collections.emptyList())), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("Runner 외래키와 ReviewStatus 로 러너 게시글을 조회한다.") + @Test + void readRunnerPostsByRunnerIdAndReviewStatus() { + // given + final Member member = memberCommandRepository.save(MemberFixture.createEthan()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + + final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline(now().plusHours(100))); + final RunnerPost savedRunnerPost = runnerPostQueryRepository.save(runnerPost); + + // when + final PageParams pageParams = new PageParams(null, 10); + final PageResponse actual + = runnerPostQueryService.pageRunnerPostByRunnerIdAndReviewStatus(pageParams, runner.getId(), NOT_STARTED); + final PageResponse expected = PageResponse.of( + List.of(RunnerPostResponse.SimpleByRunner.of(savedRunnerPost, 0L, Collections.emptyList())), + pageParams + ); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("Member 가 RunnerPost 에 지원한 이력이 있을 경우 true 를 반환한다.") + @Test + void existsRunnerPostApplicantByRunnerPostIdAndMemberId() { + // given + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporterHyena = supporterQueryRepository.save(SupporterFixture.create(savedMemberHyena)); + + final RunnerPost savedRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); + + supporterRunnerPostQueryRepository.save(SupporterRunnerPostFixture.create(savedRunnerPost, savedSupporterHyena)); + + // when + final boolean isApplicantHistoryExist = runnerPostQueryService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( + savedRunnerPost.getId(), + savedMemberHyena.getId() + ); + + // then + assertThat(isApplicantHistoryExist).isTrue(); + } + + @DisplayName("Member 가 RunnerPost 에 지원한 이력을 조회할 때 RunnerPost 자체가 없으면 false 를 반환한다.") + @Test + void existsRunnerPostApplicantByRunnerPostIdAndMemberId_if_runnerPost_is_not_exist_then_return_false() { + // given + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); + + // when + final Long notExistRunnerPostId = -1L; + final boolean isApplicantHistoryExist = runnerPostQueryService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( + notExistRunnerPostId, + savedMemberHyena.getId() + ); + + // then + assertThat(isApplicantHistoryExist).isFalse(); + } + + @DisplayName("Member 가 RunnerPost 에 지원한 이력이 없을 경우 false 를 반환한다.") + @Test + void existsRunnerPostApplicantByRunnerPostIdAndMemberId_if_member_is_not_exist_then_return_false() { + // given + final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner savedRunnerDitoo = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); + + final RunnerPost savedRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); + + // when + final Long notExistMemberId = -1L; + final boolean isApplicantHistoryExist = runnerPostQueryService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( + savedRunnerPost.getId(), + notExistMemberId + ); + + // then + assertThat(isApplicantHistoryExist).isFalse(); + } + + @DisplayName("RunnerId 와 ReviewStatus 로 러너 게시글 개수를 조회한다.") + @Test + void countRunnerPostByRunnerIdAndReviewStatus() { + // given + final Member member = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + + final long expected = 3L; + for (long i = 0; i < expected; i++) { + runnerPostCommandRepository.save(RunnerPostFixture.create(runner, new Deadline(now().plusHours(100)))); + } + + // when + final long actual = runnerPostQueryService.countRunnerPostByRunnerIdAndReviewStatus(runner.getId(), NOT_STARTED); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("SupporterId 로 ReviewStatus 가 NOT_STARTED 인 러너 게시글 개수를 조회한다.") + @Test + void countRunnerPostBySupporterIdAndReviewStatus_NOT_STARTED() { + // given + final Member member = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + final RunnerPost runnerPost = runnerPostCommandRepository.save(RunnerPostFixture.create(runner, new Deadline(now().plusHours(100)))); + + final Member supporterMember = memberCommandRepository.save(MemberFixture.createDitoo()); + final Supporter supporter = supporterQueryRepository.save(SupporterFixture.create(supporterMember)); + + supporterRunnerPostCommandRepository.save(SupporterRunnerPostFixture.create(runnerPost, supporter)); + + // when + final long expected = 1L; + final long actual = runnerPostQueryService.countRunnerPostBySupporterIdAndReviewStatus(supporter.getId(), NOT_STARTED); + + // then + assertThat(actual).isEqualTo(expected); + } + + @DisplayName("SupporterId 로 ReviewStatus 가 NOT_STARTED 이 아닌 러너 게시글 개수를 조회한다.") + @Test + void countRunnerPostBySupporterIdAndReviewStatus_except_NOT_STARTED() { + // given + final Member member = memberCommandRepository.save(MemberFixture.createDitoo()); + final Runner runner = runnerQueryRepository.save(RunnerFixture.createRunner(member)); + final RunnerPost runnerPost = runnerPostCommandRepository.save(RunnerPostFixture.create(runner, new Deadline(now().plusHours(100)))); + + final Member supporterMember = memberCommandRepository.save(MemberFixture.createDitoo()); + final Supporter supporter = supporterQueryRepository.save(SupporterFixture.create(supporterMember)); + + supporterRunnerPostCommandRepository.save(SupporterRunnerPostFixture.create(runnerPost, supporter)); + runnerPost.assignSupporter(supporter); + + // when + final long expected = 1L; + final long actual = runnerPostQueryService.countRunnerPostBySupporterIdAndReviewStatus(supporter.getId(), IN_PROGRESS); + + // then + assertThat(actual).isEqualTo(expected); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepositoryTest.java deleted file mode 100644 index 888d458c8..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostReadRepositoryTest.java +++ /dev/null @@ -1,231 +0,0 @@ -package touch.baton.domain.runnerpost.repository; - -import jakarta.persistence.EntityManager; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountDto; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountMappingDto; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.vo.TagReducedName; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.RunnerPostTagFixture; -import touch.baton.fixture.domain.SupporterFixture; -import touch.baton.fixture.domain.SupporterRunnerPostFixture; -import touch.baton.fixture.domain.TagFixture; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; -import static touch.baton.fixture.vo.TagNameFixture.tagName; - -class RunnerPostReadRepositoryTest extends RepositoryTestConfig { - - @Autowired - private RunnerPostReadRepository runnerPostReadRepository; - - @Autowired - private EntityManager em; - - @DisplayName("러너 게시글 식별자값 목록으로 서포터 지원자 수를 조회에 성공한다.") - @Test - void countApplicantsByRunnerPostIds() { - // given - final Runner hyenaRunner = persistRunner(MemberFixture.createHyena()); - final Supporter ditooSupporter = persistSupporter(MemberFixture.createDitoo()); - final Supporter ethanSupporter = persistSupporter(MemberFixture.createEthan()); - final Supporter judySupporter = persistSupporter(MemberFixture.createJudy()); - - final RunnerPost runnerPostOne = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostTwo = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostThree = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostFour = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostFive = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostSix = persistRunnerPost(hyenaRunner); - - persistApplicant(ditooSupporter, runnerPostOne); - persistApplicant(ethanSupporter, runnerPostOne); - persistApplicant(judySupporter, runnerPostOne); - - persistApplicant(ditooSupporter, runnerPostTwo); - persistApplicant(ethanSupporter, runnerPostTwo); - - persistApplicant(ditooSupporter, runnerPostThree); - - em.flush(); - em.close(); - - // when - final List actual = runnerPostReadRepository.countApplicantsByRunnerPostIds(List.of( - runnerPostTwo.getId(), - runnerPostThree.getId(), - runnerPostFour.getId(), - runnerPostFive.getId(), - runnerPostSix.getId(), - runnerPostOne.getId() - )); - - // then - final List expected = List.of( - new ApplicantCountDto(runnerPostTwo.getId(), 2L), - new ApplicantCountDto(runnerPostThree.getId(), 1L), - new ApplicantCountDto(runnerPostFour.getId(), 0L), - new ApplicantCountDto(runnerPostFive.getId(), 0L), - new ApplicantCountDto(runnerPostSix.getId(), 0L), - new ApplicantCountDto(runnerPostOne.getId(), 3L) - ); - - assertThat(actual).containsExactlyInAnyOrderElementsOf(expected); - } - - @DisplayName("러너 게시글 식별자값 목록으로 서포터 지원자 수 매핑 정보 조회에 성공한다.") - @Test - void findApplicantCountMappingByRunnerPostIds() { - // given - final Runner hyenaRunner = persistRunner(MemberFixture.createHyena()); - final Supporter ditooSupporter = persistSupporter(MemberFixture.createDitoo()); - final Supporter ethanSupporter = persistSupporter(MemberFixture.createEthan()); - final Supporter judySupporter = persistSupporter(MemberFixture.createJudy()); - - final RunnerPost runnerPostOne = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostTwo = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostThree = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostFour = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostFive = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostSix = persistRunnerPost(hyenaRunner); - - persistApplicant(ditooSupporter, runnerPostOne); - persistApplicant(ethanSupporter, runnerPostOne); - persistApplicant(judySupporter, runnerPostOne); - - persistApplicant(ditooSupporter, runnerPostTwo); - persistApplicant(ethanSupporter, runnerPostTwo); - - persistApplicant(ditooSupporter, runnerPostThree); - - em.flush(); - em.close(); - - // when - final ApplicantCountMappingDto actual = runnerPostReadRepository.findApplicantCountMappingByRunnerPostIds(List.of( - runnerPostTwo.getId(), - runnerPostThree.getId(), - runnerPostFour.getId(), - runnerPostFive.getId(), - runnerPostSix.getId(), - runnerPostOne.getId() - )); - - // then - final ApplicantCountMappingDto expected = new ApplicantCountMappingDto(Map.of( - runnerPostTwo.getId(), 2L, - runnerPostThree.getId(), 1L, - runnerPostFour.getId(), 0L, - runnerPostFive.getId(), 0L, - runnerPostSix.getId(), 0L, - runnerPostOne.getId(), 3L - )); - - assertThat(actual).isEqualTo(expected); - } - - private Runner persistRunner(final Member member) { - em.persist(member); - final Runner runner = RunnerFixture.createRunner(member); - em.persist(runner); - - return runner; - } - - private Supporter persistSupporter(final Member member) { - em.persist(member); - final Supporter supporter = SupporterFixture.create(member); - em.persist(supporter); - - return supporter; - } - - private RunnerPost persistRunnerPost(final Runner runner) { - final RunnerPost runnerPostOne = RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(100))); - em.persist(runnerPostOne); - - return runnerPostOne; - } - - private SupporterRunnerPost persistApplicant(final Supporter supporter, final RunnerPost runnerPost) { - final SupporterRunnerPost applicant = SupporterRunnerPostFixture.create(runnerPost, supporter); - em.persist(applicant); - - return applicant; - } - - @DisplayName("축약된 태그 이름과 리뷰 상태로 러너 게시글 페이징 조회에 성공한다.") - @Test - void findByTagReducedNameAndReviewStatus() { - // given - final Runner hyenaRunner = persistRunner(MemberFixture.createHyena()); - - final RunnerPost runnerPostOne = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostTwo = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostThree = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostFour = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostFive = persistRunnerPost(hyenaRunner); - final RunnerPost runnerPostSix = persistRunnerPost(hyenaRunner); - - final Tag javaTag = persistTag("자바"); - final Tag springTag = persistTag("스프링"); - - persistRunnerPostTag(runnerPostOne, javaTag); - persistRunnerPostTag(runnerPostTwo, javaTag); - persistRunnerPostTag(runnerPostThree, javaTag); - persistRunnerPostTag(runnerPostFour, springTag); - persistRunnerPostTag(runnerPostFive, javaTag); - persistRunnerPostTag(runnerPostSix, springTag); - - em.flush(); - em.close(); - - // when - final PageRequest pageOne = PageRequest.of(0, 10); - - final Page foundRunnerPosts = runnerPostReadRepository.findByTagReducedNameAndReviewStatus( - pageOne, - TagReducedName.from("자바"), - ReviewStatus.NOT_STARTED - ); - - // then - final List expected = List.of(runnerPostOne, runnerPostTwo, runnerPostThree, runnerPostFive); - - assertThat(foundRunnerPosts.getContent()).isEqualTo(expected); - } - - private Tag persistTag(final String tagName) { - final Tag javaTag = TagFixture.create(tagName(tagName)); - em.persist(javaTag); - - return javaTag; - } - - private RunnerPostTag persistRunnerPostTag(final RunnerPost runnerPost, final Tag tag) { - final RunnerPostTag runnerPostTag = RunnerPostTagFixture.create(runnerPost, tag); - em.persist(runnerPostTag); - - return runnerPostTag; - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryReadTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryReadTest.java deleted file mode 100644 index 602ded7f1..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryReadTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package touch.baton.domain.runnerpost.repository; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.tag.RunnerPostTags; -import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; - -class RunnerPostRepositoryReadTest extends RepositoryTestConfig { - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private RunnerRepository runnerRepository; - - @Autowired - private RunnerPostRepository runnerPostRepository; - - @DisplayName("RunnerPost 식별자값으로 RunnerPost 을 삭제한다.") - @Test - void success_deleteByRunnerPostId() { - // given - final Member member = Member.builder() - .memberName(new MemberName("헤에디주")) - .socialId(new SocialId("testSocialId")) - .oauthId(new OauthId("dsigjh98gh230gn2oinv913bcuo23nqovbvu93b12voi3bc31j")) - .githubUrl(new GithubUrl("github.com/hyena0608")) - .company(new Company("우아한형제들")) - .imageUrl(new ImageUrl("홍혁준")) - .build(); - final Member saveMember = memberRepository.saveAndFlush(member); - - final Runner runner = Runner.builder() - .member(saveMember) - .runnerTechnicalTags(RunnerTechnicalTagsFixture.create(new ArrayList<>())) - .build(); - - final Runner saveRunner = runnerRepository.saveAndFlush(runner); - - final RunnerPost runnerPost = RunnerPost.builder() - .title(new Title("제 코드 리뷰 좀 해주세요!!")) - .implementedContents(new ImplementedContents("제 코드는 클린코드가 맞을까요?")) - .curiousContents(new CuriousContents("저는 클린코드가 궁금해요.")) - .postscriptContents(new PostscriptContents("저 상처 잘 받으니깐 부드럽게 말해주세요.")) - .deadline(new Deadline(LocalDateTime.now())) - .pullRequestUrl(new PullRequestUrl("https://")) - .watchedCount(new WatchedCount(1)) - .runnerPostTags(new RunnerPostTags(new ArrayList<>())) - .reviewStatus(ReviewStatus.NOT_STARTED) - .isReviewed(IsReviewed.notReviewed()) - .runner(saveRunner) - .supporter(null) - .build(); - final Long saveRunnerPostId = runnerPostRepository.saveAndFlush(runnerPost).getId(); - - // when - runnerPostRepository.deleteById(saveRunnerPostId); - - final Optional maybeRunnerPost = runnerPostRepository.findById(saveRunnerPostId); - - // then - assertThat(maybeRunnerPost).isNotPresent(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryTest.java deleted file mode 100644 index f4dba6fef..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/RunnerPostRepositoryTest.java +++ /dev/null @@ -1,162 +0,0 @@ -package touch.baton.domain.runnerpost.repository; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.repository.SupporterRepository; -import touch.baton.domain.supporter.repository.SupporterRunnerPostRepository; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.SupporterFixture; -import touch.baton.fixture.domain.SupporterRunnerPostFixture; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.SoftAssertions.assertSoftly; - -class RunnerPostRepositoryTest extends RepositoryTestConfig { - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private RunnerRepository runnerRepository; - - @Autowired - private SupporterRepository supporterRepository; - - @Autowired - private RunnerPostRepository runnerPostRepository; - - @Autowired - private SupporterRunnerPostRepository supporterRunnerPostRepository; - - @DisplayName("Supporter 식별자값과 ReviewStatus 로 연관된 RunnerPost 를 페이징하여 조회한다.") - @Test - void findBySupporterIdAndReviewStatus() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - final Member savedMemberEthan = memberRepository.save(MemberFixture.createEthan()); - final Runner savedRunnerEthan = runnerRepository.save(RunnerFixture.createRunner(savedMemberEthan)); - final Member savedMemberJudy = memberRepository.save(MemberFixture.createJudy()); - final Runner savedRunnerJudy = runnerRepository.save(RunnerFixture.createRunner(savedMemberJudy)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost runnerPostOne = RunnerPostFixture.create(savedRunnerDitoo, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostOne = runnerPostRepository.save(runnerPostOne); - final RunnerPost runnerPostTwo = RunnerPostFixture.create(savedRunnerEthan, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostTwo = runnerPostRepository.save(runnerPostTwo); - final RunnerPost runnerPostThree = RunnerPostFixture.create(savedRunnerJudy, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostThree = runnerPostRepository.save(runnerPostThree); - - savedRunnerPostOne.assignSupporter(savedSupporterHyena); - savedRunnerPostOne.updateReviewStatus(ReviewStatus.DONE); - savedRunnerPostTwo.assignSupporter(savedSupporterHyena); - savedRunnerPostTwo.updateReviewStatus(ReviewStatus.DONE); - savedRunnerPostThree.assignSupporter(savedSupporterHyena); - savedRunnerPostThree.updateReviewStatus(ReviewStatus.DONE); - - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostOne, savedSupporterHyena)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostTwo, savedSupporterHyena)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostThree, savedSupporterHyena)); - - // when - final PageRequest pageOne = PageRequest.of(0, 2); - final PageRequest pageTwo = PageRequest.of(1, 2); - - final Page pageOneRunnerPosts - = runnerPostRepository.findBySupporterIdAndReviewStatus(pageOne, savedSupporterHyena.getId(), ReviewStatus.DONE); - final Page pageTwoRunnerPosts - = runnerPostRepository.findBySupporterIdAndReviewStatus(pageTwo, savedSupporterHyena.getId(), ReviewStatus.DONE); - - // then - assertSoftly(softly -> { - softly.assertThat(pageOneRunnerPosts.getContent()).containsExactly(savedRunnerPostOne, savedRunnerPostTwo); - softly.assertThat(pageTwoRunnerPosts.getContent()).containsExactly(savedRunnerPostThree); - }); - } - - @DisplayName("join 한 SupporterRunnerPost의 Supporter 외래키가 Supporter 식별자값과 같고, ReviewStatus 로 연관된 RunnerPost 를 페이징하여 조회한다.") - @Test - void joinSupporterRunnerPostBySupporterIdAndReviewStatus() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - final Member savedMemberEthan = memberRepository.save(MemberFixture.createEthan()); - final Runner savedRunnerEthan = runnerRepository.save(RunnerFixture.createRunner(savedMemberEthan)); - final Member savedMemberJudy = memberRepository.save(MemberFixture.createJudy()); - final Runner savedRunnerJudy = runnerRepository.save(RunnerFixture.createRunner(savedMemberJudy)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedApplicantHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost runnerPostOne = RunnerPostFixture.create(savedRunnerDitoo, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostOne = runnerPostRepository.save(runnerPostOne); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostOne, savedApplicantHyena)); - final RunnerPost runnerPostTwo = RunnerPostFixture.create(savedRunnerEthan, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostTwo = runnerPostRepository.save(runnerPostTwo); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostTwo, savedApplicantHyena)); - final RunnerPost runnerPostThree = RunnerPostFixture.create(savedRunnerJudy, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostThree = runnerPostRepository.save(runnerPostThree); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostThree, savedApplicantHyena)); - - // when - final PageRequest pageOne = PageRequest.of(0, 2); - final PageRequest pageTwo = PageRequest.of(1, 2); - - final Page pageOneRunnerPosts - = runnerPostRepository.joinSupporterRunnerPostBySupporterIdAndReviewStatus(pageOne, savedApplicantHyena.getId(), ReviewStatus.NOT_STARTED); - final Page pageTwoRunnerPosts - = runnerPostRepository.joinSupporterRunnerPostBySupporterIdAndReviewStatus(pageTwo, savedApplicantHyena.getId(), ReviewStatus.NOT_STARTED); - - // then - assertSoftly(softly -> { - softly.assertThat(pageOneRunnerPosts.getContent()).containsExactly(savedRunnerPostOne, savedRunnerPostTwo); - softly.assertThat(pageTwoRunnerPosts.getContent()).containsExactly(savedRunnerPostThree); - }); - } - - @DisplayName("Runner 식별자값과 ReviewStatus 로 연관된 RunnerPost 를 페이징하여 조회한다.") - @Test - void findByRunnerIdAndReviewStatus() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final RunnerPost runnerPostOne = RunnerPostFixture.create(savedRunnerDitoo, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostOne = runnerPostRepository.save(runnerPostOne); - final RunnerPost runnerPostTwo = RunnerPostFixture.create(savedRunnerDitoo, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostTwo = runnerPostRepository.save(runnerPostTwo); - final RunnerPost runnerPostThree = RunnerPostFixture.create(savedRunnerDitoo, new Deadline(LocalDateTime.now().plusHours(100))); - final RunnerPost savedRunnerPostThree = runnerPostRepository.save(runnerPostThree); - - // when - final PageRequest pageOne = PageRequest.of(0, 2); - final PageRequest pageTwo = PageRequest.of(1, 2); - - final Page pageOneRunnerPosts - = runnerPostRepository.findByRunnerIdAndReviewStatus(pageOne, savedRunnerDitoo.getId(), ReviewStatus.NOT_STARTED); - final Page pageTwoRunnerPosts - = runnerPostRepository.findByRunnerIdAndReviewStatus(pageTwo, savedRunnerDitoo.getId(), ReviewStatus.NOT_STARTED); - - assertSoftly(softly -> { - softly.assertThat(pageOneRunnerPosts.getContent()).containsExactly(savedRunnerPostOne, savedRunnerPostTwo); - softly.assertThat(pageTwoRunnerPosts.getContent()).containsExactly(savedRunnerPostThree); - }); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/SupporterRunnerPostRepositoryReadTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/SupporterRunnerPostRepositoryReadTest.java deleted file mode 100644 index 08fc35c3e..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/SupporterRunnerPostRepositoryReadTest.java +++ /dev/null @@ -1,105 +0,0 @@ -package touch.baton.domain.runnerpost.repository; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.repository.SupporterRepository; -import touch.baton.domain.supporter.repository.SupporterRunnerPostRepository; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.SupporterFixture; -import touch.baton.fixture.domain.SupporterRunnerPostFixture; - -import java.time.LocalDateTime; -import java.util.List; - -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; - -class SupporterRunnerPostRepositoryReadTest extends RepositoryTestConfig { - - @Autowired - private SupporterRunnerPostRepository supporterRunnerPostRepository; - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private RunnerRepository runnerRepository; - - @Autowired - private SupporterRepository supporterRepository; - - @Autowired - private RunnerPostRepository runnerPostRepository; - - @DisplayName("RunnerPostId 와 SupporterId 로 존재 유무를 확인할 수 있다.") - @Test - void existsByRunnerPostIdAndSupporterId() { - // given - final Member ehtanMember = memberRepository.save(MemberFixture.createEthan()); - final Runner runnerPostOwner = runnerRepository.save(RunnerFixture.createRunner(ehtanMember)); - final RunnerPost runner = runnerPostRepository.save(RunnerPostFixture.create(runnerPostOwner, - deadline(LocalDateTime.now().plusDays(10)))); - - final Member hyenaMember = memberRepository.save(MemberFixture.createHyena()); - final Supporter supporter = supporterRepository.save(SupporterFixture.create(hyenaMember)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(runner, supporter)); - - final Long notSavedRunnerPostId = -1L; - final Long notSavedSupporter = -1L; - - // when, then - assertSoftly(softly -> { - softly.assertThat(supporterRunnerPostRepository.existsByRunnerPostIdAndSupporterId(runner.getId(), supporter.getId())).isTrue(); - softly.assertThat(supporterRunnerPostRepository.existsByRunnerPostIdAndSupporterId(notSavedRunnerPostId, supporter.getId())).isFalse(); - softly.assertThat(supporterRunnerPostRepository.existsByRunnerPostIdAndSupporterId(runner.getId(), notSavedSupporter)).isFalse(); - } - ); - } - - @DisplayName("RunnerPostId 로 지원한 서포터의 수를 확인할 수 있다.") - @Test - void countByRunnerPostIds() { - // given - final Member ehtanMember = memberRepository.save(MemberFixture.createEthan()); - final Runner runnerPostOwner = runnerRepository.save(RunnerFixture.createRunner(ehtanMember)); - final RunnerPost firstRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(runnerPostOwner, - deadline(LocalDateTime.now().plusDays(10)))); - final RunnerPost twoRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(runnerPostOwner, - deadline(LocalDateTime.now().plusDays(10)))); - - final Member hyenaMember = memberRepository.save(MemberFixture.createHyena()); - final Supporter hyenaSupporter = supporterRepository.save(SupporterFixture.create(hyenaMember)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(firstRunnerPost, hyenaSupporter)); - - final Member judyMember = memberRepository.save(MemberFixture.createJudy()); - final Supporter judySupporter = supporterRepository.save(SupporterFixture.create(judyMember)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(firstRunnerPost, judySupporter)); - - // when - final List applicantCounts = supporterRunnerPostRepository.countByRunnerPostIds(List.of(firstRunnerPost.getId(), twoRunnerPost.getId())); - - final Long actualSecondRunnerPostApplicantsCount = applicantCounts.get(0); - final Long actualFirstRunnerPostApplicantsCount = applicantCounts.get(1); - final Long expectedSecondRunnerPostApplicantsCount = 0L; - final Long expectedFirstRunnerPostApplicantsCount = 2L; - final int expectedSize = 2; - - // when, then - assertSoftly(softly -> { - softly.assertThat(applicantCounts.size()).isEqualTo(expectedSize); - softly.assertThat(actualSecondRunnerPostApplicantsCount).isEqualTo(expectedSecondRunnerPostApplicantsCount); - softly.assertThat(actualFirstRunnerPostApplicantsCount).isEqualTo(expectedFirstRunnerPostApplicantsCount); - } - ); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/read/RunnerPostRepositoryReadTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/read/RunnerPostRepositoryReadTest.java deleted file mode 100644 index 16ccbc670..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/repository/read/RunnerPostRepositoryReadTest.java +++ /dev/null @@ -1,183 +0,0 @@ -package touch.baton.domain.runnerpost.repository.read; - -import jakarta.persistence.EntityManager; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.RunnerPostRepository; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.repository.RunnerPostTagRepository; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.RunnerPostTagFixture; -import touch.baton.fixture.domain.TagFixture; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.NOT_STARTED; -import static touch.baton.fixture.domain.RunnerPostTagsFixture.runnerPostTags; -import static touch.baton.fixture.vo.CuriousContentsFixture.curiousContents; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; -import static touch.baton.fixture.vo.ImplementedContentsFixture.implementedContents; -import static touch.baton.fixture.vo.PostscriptContentsFixture.postscriptContents; -import static touch.baton.fixture.vo.PullRequestUrlFixture.pullRequestUrl; -import static touch.baton.fixture.vo.TitleFixture.title; -import static touch.baton.fixture.vo.WatchedCountFixture.watchedCount; -import static touch.baton.util.TestDateFormatUtil.createExpireDate; - -class RunnerPostRepositoryReadTest extends RepositoryTestConfig { - - @Autowired - private RunnerPostRepository runnerPostRepository; - - @Autowired - private RunnerPostTagRepository runnerPostTagRepository; - - @Autowired - private EntityManager em; - - @DisplayName("RunnerPost 식별자로 RunnerPostTag 목록을 조회할 때 Tag 가 있으면 조회된다.") - @Test - void findRunnerPostTagsById_exist() { - // given - final Member ditoo = MemberFixture.createDitoo(); - em.persist(ditoo); - final Runner runner = RunnerFixture.createRunner(ditoo); - em.persist(runner); - - final RunnerPost runnerPost = RunnerPostFixture.create(title("제 코드를 리뷰해주세요"), - implementedContents("제 코드의 내용은 이렇습니다."), - curiousContents("저는 이것이 궁금합니다."), - postscriptContents("잘 부탁드립니다."), - pullRequestUrl("https://"), - deadline(LocalDateTime.now().plusHours(10)), - watchedCount(0), - NOT_STARTED, - IsReviewed.notReviewed(), - runner, - null, - runnerPostTags(new ArrayList<>())); - runnerPostRepository.save(runnerPost); - - final Tag java = TagFixture.createJava(); - em.persist(java); - final Tag spring = TagFixture.createSpring(); - em.persist(spring); - final RunnerPostTag javaRunnerPostTag = RunnerPostTagFixture.create(runnerPost, java); - final RunnerPostTag springRunnerPostTag = RunnerPostTagFixture.create(runnerPost, spring); - - runnerPost.addAllRunnerPostTags(List.of(javaRunnerPostTag, springRunnerPostTag)); - - em.flush(); - em.close(); - - // when - final List expected = runnerPostTagRepository.joinTagByRunnerPostId(runnerPost.getId()); - - // then - assertThat(expected).containsExactly(javaRunnerPostTag, springRunnerPostTag); - } - - @DisplayName("Runner 식별자로 RunnerPost 목록을 조회한다.") - @Test - void findByRunnerId() { - // given - final Member ditoo = MemberFixture.createDitoo(); - em.persist(ditoo); - final Runner runner = RunnerFixture.createRunner(ditoo); - em.persist(runner); - - em.flush(); - em.close(); - - final RunnerPost runnerPost = RunnerPostFixture.create(title("제 코드를 리뷰해주세요"), - implementedContents("제 코드의 내용은 이렇습니다."), - curiousContents("저는 이것이 궁금합니다."), - postscriptContents("잘 부탁드립니다."), - pullRequestUrl("https://"), - deadline(LocalDateTime.now().plusHours(10)), - watchedCount(0), - NOT_STARTED, - IsReviewed.notReviewed(), - runner, - null, - runnerPostTags(new ArrayList<>())); - runnerPostRepository.save(runnerPost); - - // when - final List actual = runnerPostRepository.findByRunnerId(runner.getId()); - - // then - assertThat(actual).containsExactly(runnerPost); - } - - @DisplayName("id 오름차순으로 게시글 페이징 조회한다") - @Test - void findByReviewStatus_sort_by_id() { - // given - final Member ditoo = MemberFixture.createDitoo(); - em.persist(ditoo); - final Runner runner = RunnerFixture.createRunner(ditoo); - em.persist(runner); - final Long runnerId = runner.getId(); - final LocalDateTime createdAt = createExpireDate(LocalDateTime.now()); - - insertRunnerPostByNativeQuery(1L, createdAt, runnerId); - insertRunnerPostByNativeQuery(3L, createdAt, runnerId); - insertRunnerPostByNativeQuery(2L, createdAt, runnerId); - - // when - final PageRequest pageable = PageRequest.of(0, 10, Sort.by(Sort.Order.desc("id"))); - - final Page actual = runnerPostRepository.findByReviewStatus(pageable, NOT_STARTED); - - // then - assertSoftly(softly -> { - softly.assertThat(actual.getTotalElements()).isEqualTo(3); - - List runnerPosts = actual.getContent(); - softly.assertThat(runnerPosts.get(0).getId()).isEqualTo(3L); - softly.assertThat(runnerPosts.get(1).getId()).isEqualTo(2L); - softly.assertThat(runnerPosts.get(2).getId()).isEqualTo(1L); - - softly.assertThat(runnerPosts.stream() - .filter(runnerPost -> runnerPost.getCreatedAt().isEqual(createdAt)) - .count()).isEqualTo(3); - }); - } - - private void insertRunnerPostByNativeQuery(final long value, final LocalDateTime createdAt, final Long runnerId) { - em.createNativeQuery(""" - insert into runner_post (id, title, implemented_contents, curious_contents, postscript_contents, - pull_request_url, deadline, review_status, created_at, updated_at, runner_id) - values (:id, :title, :implemented_contents, :curious_contents, :postscript_contents, - :pull_request_url, :deadline, :review_status, :created_at, :updated_at, :runner_id) - """) - .setParameter("id", value) - .setParameter("title", "제목") - .setParameter("implemented_contents", "구현 내용") - .setParameter("curious_contents", "궁금한 내용") - .setParameter("postscript_contents", "참고 사항") - .setParameter("pull_request_url", "pr url") - .setParameter("deadline", LocalDateTime.now().plusHours(100)) - .setParameter("review_status", NOT_STARTED.name()) - .setParameter("created_at", createdAt) - .setParameter("updated_at", createdAt) - .setParameter("runner_id", runnerId) - .executeUpdate(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostReadServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostReadServiceTest.java deleted file mode 100644 index 52d9fdd7e..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostReadServiceTest.java +++ /dev/null @@ -1,154 +0,0 @@ -package touch.baton.domain.runnerpost.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.dto.ApplicantCountMappingDto; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.tag.Tag; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.SupporterFixture; -import touch.baton.fixture.domain.SupporterRunnerPostFixture; -import touch.baton.fixture.domain.TagFixture; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; -import static touch.baton.fixture.vo.TagNameFixture.tagName; - -class RunnerPostReadServiceTest extends ServiceTestConfig { - - private RunnerPostReadService runnerPostReadService; - - @BeforeEach - void setUp() { - runnerPostReadService = new RunnerPostReadService(runnerPostReadRepository); - } - - @DisplayName("러너 게시글을 태그 이름과 리뷰 상태를 조건으로 이용하여 페이징 조회에 성공한다.") - @Test - void readRunnerPostByTagNameAndReviewStatus() { - // given - final Member hyenaMember = memberRepository.save(MemberFixture.createHyena()); - final Runner hyenaRunner = runnerRepository.save(RunnerFixture.createRunner(hyenaMember)); - - final Tag javaTag = tagRepository.save(TagFixture.create(tagName("자바"))); - final Tag springTag = tagRepository.save(TagFixture.create(tagName("스프링"))); - - final RunnerPost expectedRunnerPostOne = runnerPostRepository.save(RunnerPostFixture.create( - hyenaRunner, - deadline(LocalDateTime.now().plusHours(100)), - List.of(javaTag, springTag) - )); - - final RunnerPost expectedRunnerPostTwo = runnerPostRepository.save(RunnerPostFixture.create( - hyenaRunner, - deadline(LocalDateTime.now().plusHours(100)), - List.of(javaTag) - )); - - runnerPostRepository.save(RunnerPostFixture.create( - hyenaRunner, - deadline(LocalDateTime.now().plusHours(100)), - List.of(springTag) - )); - - // when - final PageRequest pageOne = PageRequest.of(0, 10, Sort.by(Sort.Order.desc("id"))); - final Page actual = runnerPostReadService.readRunnerPostByTagNameAndReviewStatus( - pageOne, - javaTag.getTagName().getValue(), - ReviewStatus.NOT_STARTED - ); - - // then - assertSoftly(softly -> { - softly.assertThat(actual.isFirst()).isTrue(); - softly.assertThat(actual.isEmpty()).isFalse(); - softly.assertThat(actual.isLast()).isTrue(); - softly.assertThat(actual.getTotalPages()).isEqualTo(1); - softly.assertThat(actual.getTotalElements()).isEqualTo(2); - softly.assertThat(actual.getSize()).isEqualTo(10); - softly.assertThat(actual.getNumberOfElements()).isEqualTo(2); - softly.assertThat(actual.getNumber()).isEqualTo(0); - softly.assertThat(actual.getContent()).containsExactly(expectedRunnerPostTwo, expectedRunnerPostOne); - }); - } - - @DisplayName("러너 게시글 식별자값 목록으로 서포터 지원자 수 카운트(count)에 성공한다.") - @Test - void readApplicantCountsByRunnerPostIds() { - // given - final Member hyenaMember = memberRepository.save(MemberFixture.createHyena()); - final Runner hyenaRunner = runnerRepository.save(RunnerFixture.createRunner(hyenaMember)); - - final Member ditooMember = memberRepository.save(MemberFixture.createDitoo()); - final Supporter supporterDitoo = supporterRepository.save(SupporterFixture.create(ditooMember)); - - final Member ethanMember = memberRepository.save(MemberFixture.createEthan()); - final Supporter supporterEthan = supporterRepository.save(SupporterFixture.create(ethanMember)); - - final Member judyMember = memberRepository.save(MemberFixture.createJudy()); - final Supporter supporterJudy = supporterRepository.save(SupporterFixture.create(judyMember)); - - final Tag javaTag = tagRepository.save(TagFixture.create(tagName("자바"))); - final Tag springTag = tagRepository.save(TagFixture.create(tagName("스프링"))); - - final RunnerPost savedRunnerPostOne = runnerPostRepository.save(RunnerPostFixture.create( - hyenaRunner, - deadline(LocalDateTime.now().plusHours(100)), - List.of(javaTag, springTag) - )); - - final RunnerPost savedRunnerPostTwo = runnerPostRepository.save(RunnerPostFixture.create( - hyenaRunner, - deadline(LocalDateTime.now().plusHours(100)), - List.of(javaTag) - )); - - final RunnerPost savedRunnerPostThree = runnerPostRepository.save(RunnerPostFixture.create( - hyenaRunner, - deadline(LocalDateTime.now().plusHours(100)), - List.of(springTag) - )); - - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostOne, supporterDitoo)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostOne, supporterEthan)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostOne, supporterJudy)); - - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostThree, supporterDitoo)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPostThree, supporterEthan)); - - // when - final List runnerPostIds = List.of( - savedRunnerPostOne.getId(), - savedRunnerPostTwo.getId(), - savedRunnerPostThree.getId() - ); - - final ApplicantCountMappingDto actual = runnerPostReadService.readApplicantCountMappingByRunnerPostIds(runnerPostIds); - - // then - final ApplicantCountMappingDto expected = new ApplicantCountMappingDto(Map.of( - savedRunnerPostOne.getId(), 3L, - savedRunnerPostTwo.getId(), 0L, - savedRunnerPostThree.getId(), 2L - )); - - assertThat(actual).isEqualTo(expected); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceDeleteTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceDeleteTest.java deleted file mode 100644 index 0d8a2d320..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceDeleteTest.java +++ /dev/null @@ -1,119 +0,0 @@ -package touch.baton.domain.runnerpost.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.exception.RunnerPostBusinessException; -import touch.baton.domain.supporter.Supporter; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.SupporterFixture; -import touch.baton.fixture.domain.SupporterRunnerPostFixture; - -import java.time.LocalDateTime; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; - -class RunnerPostServiceDeleteTest extends ServiceTestConfig { - - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - runnerPostService = new RunnerPostService( - runnerPostRepository, - runnerPostTagRepository, - tagRepository, - supporterRepository, - supporterRunnerPostRepository - ); - } - - @DisplayName("RunnerPost 식별자값으로 RunnerPost 을 삭제한다.") - @Test - void success_deleteByRunnerPostId() { - // given - final Member member = memberRepository.save(MemberFixture.createDitoo()); - final Runner runner = runnerRepository.save(RunnerFixture.createRunner(member)); - final RunnerPost runnerPost = runnerPostRepository.save(RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(10)))); - - // when - runnerPostService.deleteByRunnerPostId(runnerPost.getId(), runner); - - // then - assertThat(runnerPostRepository.existsById(runnerPost.getId())).isFalse(); - } - - @DisplayName("RunnerPost 식별자값으로 존재하지 않는 RunnerPost 를 삭제 시도할 경우 예외가 발생한다.") - @Test - void fail_deleteByRunnerPostId_if_runnerPost_is_null() { - final Member member = memberRepository.save(MemberFixture.createDitoo()); - final Runner runner = runnerRepository.save(RunnerFixture.createRunner(member)); - assertThatThrownBy(() -> runnerPostService.deleteByRunnerPostId(0L, runner)) - .isInstanceOf(RunnerPostBusinessException.class); - } - - @DisplayName("RunnerPost 를 작성하지 않은 Runner 가 삭제 시도할 경우 예외가 발생한다.") - @Test - void fail_deleteByRunnerPostId_if_not_owner() { - // given - final Member memberRunnerPostOwner = memberRepository.save(MemberFixture.createDitoo()); - final Runner runnerPostOwner = runnerRepository.save(RunnerFixture.createRunner(memberRunnerPostOwner)); - final RunnerPost runnerPost = runnerPostRepository.save(RunnerPostFixture.create( - runnerPostOwner, - deadline(LocalDateTime.now().plusHours(10)) - )); - final Member memberRunnerPostNotOwner = memberRepository.save(MemberFixture.createJudy()); - final Runner runnerPostNotOwner = runnerRepository.save(RunnerFixture.createRunner(memberRunnerPostNotOwner)); - - // when & then - assertThatThrownBy(() -> runnerPostService.deleteByRunnerPostId(runnerPost.getId(), runnerPostNotOwner)) - .isInstanceOf(RunnerPostBusinessException.class); - } - - @DisplayName("NOT_STARTED 상태가 아닌 RunnerPost 를 삭제 시도할 경우 예외가 발생한다.") - @Test - void fail_deleteByRunnerPostId_if_reviewStatus_is_not_NOT_STARTED() { - // given - final Member memberRunner = memberRepository.save(MemberFixture.createDitoo()); - final Runner runner = runnerRepository.save(RunnerFixture.createRunner(memberRunner)); - final RunnerPost runnerPost = runnerPostRepository.save(RunnerPostFixture.create( - runner, - deadline(LocalDateTime.now().plusHours(10)) - )); - final Member memberSupporter = memberRepository.save(MemberFixture.createEthan()); - final Supporter supporter = supporterRepository.save(SupporterFixture.create(memberSupporter)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(runnerPost, supporter)); - runnerPost.assignSupporter(supporter); - - // when & then - assertThatThrownBy(() -> runnerPostService.deleteByRunnerPostId(runnerPost.getId(), runner)) - .isInstanceOf(RunnerPostBusinessException.class); - } - - @DisplayName("지원한 서포터가 있는 경우에 RunnerPost 를 삭제 시도할 경우 예외가 발생한다.") - @Test - void fail_deleteByRunnerPostId_if_applicant_is_exist() { - // given - final Member member = memberRepository.save(MemberFixture.createDitoo()); - final Runner runner = runnerRepository.save(RunnerFixture.createRunner(member)); - final RunnerPost runnerPost = runnerPostRepository.save(RunnerPostFixture.create( - runner, - deadline(LocalDateTime.now().plusHours(10)) - )); - final Member memberSupporter = memberRepository.save(MemberFixture.createEthan()); - final Supporter supporter = supporterRepository.save(SupporterFixture.create(memberSupporter)); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(runnerPost, supporter)); - - // when & then - assertThatThrownBy(() -> runnerPostService.deleteByRunnerPostId(runnerPost.getId(), runner)) - .isInstanceOf(RunnerPostBusinessException.class); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java deleted file mode 100644 index 570f37f6d..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/service/RunnerPostServiceReadTest.java +++ /dev/null @@ -1,360 +0,0 @@ -package touch.baton.domain.runnerpost.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.exception.RunnerPostBusinessException; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.RunnerPostTags; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.vo.TagReducedName; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; -import touch.baton.fixture.domain.SupporterFixture; -import touch.baton.fixture.domain.SupporterRunnerPostFixture; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import static java.time.LocalDateTime.now; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static org.junit.jupiter.api.Assertions.assertAll; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.IN_PROGRESS; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.NOT_STARTED; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; - -class RunnerPostServiceReadTest extends ServiceTestConfig { - - private RunnerPostService runnerPostService; - - @BeforeEach - void setUp() { - runnerPostService = new RunnerPostService( - runnerPostRepository, - runnerPostTagRepository, - tagRepository, - supporterRepository, - supporterRunnerPostRepository - ); - } - - @DisplayName("RunnerPost 식별자로 RunnerPost 를 조회한다.") - @Test - void success_findByRunnerPostId() { - // given - final Member member = Member.builder() - .memberName(new MemberName("헤에디주")) - .socialId(new SocialId("testSocialId")) - .oauthId(new OauthId("dsigjh98gh230gn2oinv913bcuo23nqovbvu93b12voi3bc31j")) - .githubUrl(new GithubUrl("github.com/hyena0608")) - .company(new Company("우아한형제들")) - .imageUrl(new ImageUrl("홍혁준")) - .build(); - memberRepository.save(member); - - final Runner runner = Runner.builder() - .member(member) - .runnerTechnicalTags(RunnerTechnicalTagsFixture.create(new ArrayList<>())) - .build(); - runnerRepository.save(runner); - - final LocalDateTime deadline = now(); - final RunnerPost runnerPost = RunnerPost.builder() - .title(new Title("제 코드 리뷰 좀 해주세요!!")) - .implementedContents(new ImplementedContents("제 코드는 클린코드가 맞을까요?")) - .curiousContents(new CuriousContents("궁금해요.")) - .postscriptContents(new PostscriptContents("잘 부탁드립니다.")) - .deadline(new Deadline(deadline)) - .pullRequestUrl(new PullRequestUrl("https://")) - .watchedCount(new WatchedCount(0)) - .runnerPostTags(new RunnerPostTags(new ArrayList<>())) - .reviewStatus(NOT_STARTED) - .isReviewed(IsReviewed.notReviewed()) - .runner(runner) - .supporter(null) - .build(); - runnerPostRepository.save(runnerPost); - - final Tag tag = Tag.builder() - .tagName(new TagName("자바")) - .tagReducedName(TagReducedName.from("자바")) - .build(); - tagRepository.save(tag); - - final RunnerPostTag runnerPostTag = RunnerPostTag.builder() - .runnerPost(runnerPost) - .tag(tag) - .build(); - runnerPost.addAllRunnerPostTags(List.of(runnerPostTag)); - - // when - final RunnerPost findRunnerPost = runnerPostService.readByRunnerPostId(runnerPost.getId()); - - // then - assertThat(findRunnerPost) - .usingRecursiveComparison() - .ignoringExpectedNullFields() - .isEqualTo(runnerPost); - } - - @DisplayName("RunnerPost 식별자로 존재하지 않는 RunnerPost 를 조회할 경우 예외가 발생한다.") - @Test - void fail_findByRunnerPostId_if_runner_post_is_null() { - assertThatThrownBy(() -> runnerPostService.readByRunnerPostId(0L)) - .isInstanceOf(RunnerPostBusinessException.class) - .hasMessage("RunnerPost 의 식별자값으로 러너 게시글을 조회할 수 없습니다."); - } - - @DisplayName("Runner 식별자값으로 RunnerPost 를 조회한다.") - @Test - void success_findByRunnerId() { - // given - final Member ditoo = MemberFixture.createDitoo(); - memberRepository.save(ditoo); - final Runner runner = RunnerFixture.createRunner(ditoo); - runnerRepository.save(runner); - final RunnerPost expected = RunnerPostFixture.create(runner, new Deadline(now().plusHours(100))); - runnerPostRepository.save(expected); - - // when - final List actual = runnerPostService.readRunnerPostsByRunnerId(runner.getId()); - - // then - assertSoftly(softly -> { - softly.assertThat(actual).hasSize(1); - softly.assertThat(actual.get(0)).isEqualTo(expected); - }); - } - - @DisplayName("ReviewStatus 로 RunnerPost 를 전체 조회한다.") - @Test - void success_readRunnerPostsByReviewStatus() { - // given - final Member memberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner runnerDitoo = runnerRepository.save(RunnerFixture.createRunner(memberDitoo)); - final Member memberJudy = memberRepository.save(MemberFixture.createJudy()); - final Supporter supporterJudy = supporterRepository.save(SupporterFixture.create(memberJudy)); - - final RunnerPost inProgressRunnerPost = RunnerPostFixture.create(runnerDitoo, deadline(now().plusHours(100))); - inProgressRunnerPost.assignSupporter(supporterJudy); - final RunnerPost savedInProgressRunnerPost = runnerPostRepository.save(inProgressRunnerPost); - - // when - final PageRequest pageable = PageRequest.of(0, 10); - final Page actualInProgressRunnerPosts = runnerPostService.readRunnerPostsByReviewStatus(pageable, IN_PROGRESS); - - // then - assertSoftly(softly -> { - softly.assertThat(actualInProgressRunnerPosts.getPageable()).isEqualTo(pageable); - softly.assertThat(actualInProgressRunnerPosts.getContent()).containsExactly(savedInProgressRunnerPost); - }); - } - - @DisplayName("Supporter 외래키와 ReviewStatus 가 NOT_STARTED 가 아닌 것으로 러너 게시글을 조회한다.") - @Test - void readRunnerPostsBySupporterIdAndReviewStatusIsNot_NOT_STARTED() { - // given - final Member savedMemberEthan = memberRepository.save(MemberFixture.createEthan()); - final Runner savedRunnerEthan = runnerRepository.save(RunnerFixture.createRunner(savedMemberEthan)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost runnerPost = RunnerPostFixture.create(savedRunnerEthan, deadline(now().plusHours(100))); - final RunnerPost savedRunnerPost = runnerPostRepository.save(runnerPost); - savedRunnerPost.assignSupporter(savedSupporterHyena); - - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(runnerPost, savedSupporterHyena)); - - // when - final PageRequest pageable = PageRequest.of(0, 10); - final Page pageRunnerPosts - = runnerPostService.readRunnerPostsBySupporterIdAndReviewStatus(pageable, savedSupporterHyena.getId(), IN_PROGRESS); - - // then - assertAll( - () -> assertThat(pageRunnerPosts.getPageable()).isEqualTo(pageable), - () -> assertThat(pageRunnerPosts.getContent()).containsExactly(savedRunnerPost) - ); - } - - @DisplayName("Runner 외래키와 ReviewStatus 로 러너 게시글을 조회한다.") - @Test - void readRunnerPostsByRunnerIdAndReviewStatus() { - // given - final Member savedMemberEthan = memberRepository.save(MemberFixture.createEthan()); - final Runner savedRunnerEthan = runnerRepository.save(RunnerFixture.createRunner(savedMemberEthan)); - - final RunnerPost runnerPost = RunnerPostFixture.create(savedRunnerEthan, deadline(now().plusHours(100))); - final RunnerPost savedRunnerPost = runnerPostRepository.save(runnerPost); - - // when - final PageRequest pageable = PageRequest.of(0, 10); - final Page pageRunnerPosts - = runnerPostService.readRunnerPostsByRunnerIdAndReviewStatus(pageable, savedRunnerEthan.getId(), ReviewStatus.NOT_STARTED); - - // then - assertAll( - () -> assertThat(pageRunnerPosts.getPageable()).isEqualTo(pageable), - () -> assertThat(pageRunnerPosts.getContent()).containsExactly(savedRunnerPost) - ); - } - - @DisplayName("RunnerPost 식별자값으로 Supporter 지원자수를 count 한다.") - @Test - void readCountByRunnerPostId() { - // given - final Member savedMemberEthan = memberRepository.save(MemberFixture.createEthan()); - final Runner savedRunnerEthan = runnerRepository.save(RunnerFixture.createRunner(savedMemberEthan)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost runnerPost = RunnerPostFixture.create(savedRunnerEthan, deadline(now().plusHours(100))); - final RunnerPost savedRunnerPost = runnerPostRepository.save(runnerPost); - savedRunnerPost.assignSupporter(savedSupporterHyena); - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(runnerPost, savedSupporterHyena)); - - // when - final long foundApplicantCount = runnerPostService.readCountByRunnerPostId(savedRunnerPost.getId()); - - // then - assertThat(foundApplicantCount).isEqualTo(1); - } - - @DisplayName("RunnerPost 식별자값으로 찾은 Supporter 지원자가 아무도 없을 경우 count 로 0을 반환한다.") - @Test - void readCountByRunnerPostId_is_null_then_return_zero() { - // given - final Member savedMemberEthan = memberRepository.save(MemberFixture.createEthan()); - final Runner savedRunnerEthan = runnerRepository.save(RunnerFixture.createRunner(savedMemberEthan)); - - final RunnerPost runnerPost = RunnerPostFixture.create(savedRunnerEthan, deadline(now().plusHours(100))); - final RunnerPost savedRunnerPost = runnerPostRepository.save(runnerPost); - - // when - final long foundApplicantCount = runnerPostService.readCountByRunnerPostId(savedRunnerPost.getId()); - - // then1 - assertThat(foundApplicantCount).isZero(); - } - - @DisplayName("Supporter 외래키와 ReviewStatus 가 NOT_STARTED 로 러너 게시글을 조회한다.") - @Test - void readRunnerPostsBySupporterIdAndReviewStatusIs_NOT_STARTED() { - // given - final Member savedMemberEthan = memberRepository.save(MemberFixture.createEthan()); - final Runner savedRunnerEthan = runnerRepository.save(RunnerFixture.createRunner(savedMemberEthan)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost runnerPost = RunnerPostFixture.create(savedRunnerEthan, deadline(now().plusHours(100))); - final RunnerPost savedRunnerPost = runnerPostRepository.save(runnerPost); - - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(runnerPost, savedSupporterHyena)); - - // when - final PageRequest pageable = PageRequest.of(0, 10); - final Page pageRunnerPosts - = runnerPostService.readRunnerPostsBySupporterIdAndReviewStatus(pageable, savedSupporterHyena.getId(), NOT_STARTED); - - // then - assertAll( - () -> assertThat(pageRunnerPosts.getPageable()).isEqualTo(pageable), - () -> assertThat(pageRunnerPosts.getContent()).containsExactly(savedRunnerPost) - ); - } - - @DisplayName("Member 가 RunnerPost 에 지원한 이력이 있을 경우 true 를 반환한다.") - @Test - void existsRunnerPostApplicantByRunnerPostIdAndMemberId() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - - supporterRunnerPostRepository.save(SupporterRunnerPostFixture.create(savedRunnerPost, savedSupporterHyena)); - - // when - final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( - savedRunnerPost.getId(), - savedMemberHyena.getId() - ); - - // then - assertThat(isApplicantHistoryExist).isTrue(); - } - - @DisplayName("Member 가 RunnerPost 에 지원한 이력을 조회할 때 RunnerPost 자체가 없으면 false 를 반환한다.") - @Test - void existsRunnerPostApplicantByRunnerPostIdAndMemberId_if_runnerPost_is_not_exist_then_return_false() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - - // when - final Long notExistRunnerPostId = -1L; - final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( - notExistRunnerPostId, - savedMemberHyena.getId() - ); - - // then - assertThat(isApplicantHistoryExist).isFalse(); - } - - @DisplayName("Member 가 RunnerPost 에 지원한 이력이 없을 경우 false 를 반환한다.") - @Test - void existsRunnerPostApplicantByRunnerPostIdAndMemberId_if_member_is_not_exist_then_return_false() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - - // when - final Long notExistMemberId = -1L; - final boolean isApplicantHistoryExist = runnerPostService.existsRunnerPostApplicantByRunnerPostIdAndMemberId( - savedRunnerPost.getId(), - notExistMemberId - ); - - // then - assertThat(isApplicantHistoryExist).isFalse(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterFeedbackTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/command/SupporterFeedbackTest.java similarity index 90% rename from backend/baton/src/test/java/touch/baton/domain/supporter/SupporterFeedbackTest.java rename to backend/baton/src/test/java/touch/baton/domain/supporter/command/SupporterFeedbackTest.java index 0e71329ef..067746f8c 100644 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterFeedbackTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/command/SupporterFeedbackTest.java @@ -1,16 +1,17 @@ -package touch.baton.domain.supporter; +package touch.baton.domain.supporter.command; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import touch.baton.domain.feedback.command.vo.Description; +import touch.baton.domain.feedback.command.vo.ReviewType; import touch.baton.domain.feedback.exception.SupporterFeedbackException; -import touch.baton.domain.feedback.vo.Description; -import touch.baton.domain.feedback.vo.ReviewType; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.supporter.vo.ReviewCount; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -22,8 +23,8 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static touch.baton.domain.feedback.SupporterFeedback.builder; -import static touch.baton.domain.runnerpost.vo.ReviewStatus.NOT_STARTED; +import static touch.baton.domain.feedback.command.SupporterFeedback.builder; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.NOT_STARTED; import static touch.baton.fixture.vo.CuriousContentsFixture.curiousContents; import static touch.baton.fixture.vo.DeadlineFixture.deadline; import static touch.baton.fixture.vo.ImplementedContentsFixture.implementedContents; diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/command/SupporterTest.java similarity index 84% rename from backend/baton/src/test/java/touch/baton/domain/supporter/SupporterTest.java rename to backend/baton/src/test/java/touch/baton/domain/supporter/command/SupporterTest.java index 47590375c..7129b7725 100644 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/SupporterTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/command/SupporterTest.java @@ -1,22 +1,23 @@ -package touch.baton.domain.supporter; +package touch.baton.domain.supporter.command; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.supporter.exception.SupporterDomainException; -import touch.baton.domain.supporter.vo.ReviewCount; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.Introduction; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.member.exception.SupporterDomainException; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.SupporterTechnicalTagFixture; import touch.baton.fixture.domain.TechnicalTagFixture; @@ -24,9 +25,7 @@ import java.util.ArrayList; import java.util.List; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.SoftAssertions.assertSoftly; class SupporterTest { diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/command/repository/SupporterQueryRepositoryTest.java similarity index 61% rename from backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/domain/supporter/command/repository/SupporterQueryRepositoryTest.java index 7f1d1c9ce..6a47eb423 100644 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/command/repository/SupporterQueryRepositoryTest.java @@ -1,37 +1,31 @@ -package touch.baton.domain.supporter.repository; +package touch.baton.domain.supporter.command.repository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.query.repository.SupporterQueryRepository; import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.SupporterFixture; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -class SupporterRepositoryTest extends RepositoryTestConfig { +class SupporterQueryRepositoryTest extends RepositoryTestConfig { @Autowired - private MemberRepository memberRepository; - - @Autowired - private SupporterRepository supporterRepository; + private SupporterQueryRepository supporterQueryRepository; @DisplayName("Supporter 식별자값으로 Member 를 패치 조인하여 조회한다.") @Test void joinMemberBySupporterId() { // given - final Member savedMember = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporter = supporterRepository.save(SupporterFixture.create(savedMember)); + final Supporter savedSupporter = persistSupporter(MemberFixture.createHyena()); // when - final Optional maybeSupporter = supporterRepository.joinMemberBySupporterId(savedSupporter.getId()); + final Optional maybeSupporter = supporterQueryRepository.joinMemberBySupporterId(savedSupporter.getId()); // then assertAll( @@ -40,7 +34,7 @@ void joinMemberBySupporterId() { () -> assertThat(maybeSupporter.get().getIntroduction()).isEqualTo(savedSupporter.getIntroduction()), () -> assertThat(maybeSupporter.get().getReviewCount()).isEqualTo(savedSupporter.getReviewCount()), () -> assertThat(maybeSupporter.get().getSupporterTechnicalTags()).isEqualTo(savedSupporter.getSupporterTechnicalTags()), - () -> assertThat(maybeSupporter.get().getMember()).isEqualTo(savedMember) + () -> assertThat(maybeSupporter.get().getMember()).isEqualTo(savedSupporter.getMember()) ); } diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/command/repository/SupporterRunnerPostCommandRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/command/repository/SupporterRunnerPostCommandRepositoryTest.java new file mode 100644 index 000000000..8a1d9a4f6 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/command/repository/SupporterRunnerPostCommandRepositoryTest.java @@ -0,0 +1,79 @@ +package touch.baton.domain.supporter.command.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.member.command.repository.SupporterRunnerPostCommandRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.fixture.domain.MemberFixture; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +class SupporterRunnerPostCommandRepositoryTest extends RepositoryTestConfig { + + @Autowired + private SupporterRunnerPostCommandRepository supporterRunnerPostCommandRepository; + + @DisplayName("RunnerPost 외래키로 된 SupporterRunnerPost 가 존재하는지 확인한다.") + @Test + void existsByRunnerPostId() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final Supporter supporter = persistSupporter(MemberFixture.createHyena()); + final RunnerPost runnerPostOfApplicantExist = persistRunnerPost(runner); + final RunnerPost runnerPostOfApplicantNotExist = persistRunnerPost(runner); + persistApplicant(supporter, runnerPostOfApplicantExist); + + // when + final boolean actualOfExist = supporterRunnerPostCommandRepository.existsByRunnerPostId(runnerPostOfApplicantExist.getId()); + final boolean actualOfNotExist = supporterRunnerPostCommandRepository.existsByRunnerPostId(runnerPostOfApplicantNotExist.getId()); + + // then + assertSoftly(softly -> { + softly.assertThat(actualOfExist).isTrue(); + softly.assertThat(actualOfNotExist).isFalse(); + }); + } + + @DisplayName("RunnerPostId 와 SupporterId 로 존재 유무를 확인할 수 있다.") + @Test + void existsByRunnerPostIdAndSupporterId() { + // given + final Runner runner = persistRunner(MemberFixture.createEthan()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter supporter = persistSupporter(MemberFixture.createHyena()); + persistApplicant(supporter, runnerPost); + + final Long notSavedRunnerPostId = -1L; + final Long notSavedSupporter = -1L; + + // when, then + assertSoftly(softly -> { + softly.assertThat(supporterRunnerPostCommandRepository.existsByRunnerPostIdAndSupporterId(runnerPost.getId(), supporter.getId())).isTrue(); + softly.assertThat(supporterRunnerPostCommandRepository.existsByRunnerPostIdAndSupporterId(notSavedRunnerPostId, supporter.getId())).isFalse(); + softly.assertThat(supporterRunnerPostCommandRepository.existsByRunnerPostIdAndSupporterId(runnerPost.getId(), notSavedSupporter)).isFalse(); + } + ); + } + + @DisplayName("서포터의 러너 게시글 리뷰 제안을 철회하는데 성공한다") + @Test + void deleteBySupporterAndRunnerPostId() { + // given + final Supporter supporter = persistSupporter(MemberFixture.createDitoo()); + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final SupporterRunnerPost supporterRunnerPost = persistApplicant(supporter, runnerPost); + + // when + supporterRunnerPostCommandRepository.deleteBySupporterIdAndRunnerPostId(supporterRunnerPost.getId(), runnerPost.getId()); + + // then + assertThat(supporterRunnerPostCommandRepository.findById(runnerPost.getId())).isNotPresent(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/command/service/SupporterCommandServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/command/service/SupporterCommandServiceTest.java new file mode 100644 index 000000000..7f6b41747 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/command/service/SupporterCommandServiceTest.java @@ -0,0 +1,40 @@ +package touch.baton.domain.supporter.command.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.service.SupporterCommandService; +import touch.baton.domain.member.command.service.dto.SupporterUpdateRequest; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.SupporterFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatCode; + + +class SupporterCommandServiceTest extends ServiceTestConfig { + + private SupporterCommandService supporterCommandService; + + @BeforeEach + void setUp() { + supporterCommandService = new SupporterCommandService(technicalTagQueryRepository, supporterTechnicalTagCommandRepository); + } + + @DisplayName("Supporter 정보를 수정한다.") + @Test + void updateSupporter() { + // given + final Member savedMember = memberCommandRepository.save(MemberFixture.createDitoo()); + final Supporter savedSupporter = supporterQueryRepository.save(SupporterFixture.create(savedMember)); + final SupporterUpdateRequest request = new SupporterUpdateRequest("디투랜드", "두나무", "소개글입니다.", List.of("golang", "rust")); + + // when, then + assertThatCode(() -> supporterCommandService.updateSupporter(savedSupporter, request)) + .doesNotThrowAnyException(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/query/repository/SupporterRunnerPostQueryRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/query/repository/SupporterRunnerPostQueryRepositoryTest.java new file mode 100644 index 000000000..3f3ec7bd6 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/query/repository/SupporterRunnerPostQueryRepositoryTest.java @@ -0,0 +1,112 @@ +package touch.baton.domain.supporter.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.member.query.repository.SupporterRunnerPostQueryRepository; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class SupporterRunnerPostQueryRepositoryTest extends RepositoryTestConfig { + + @Autowired + private SupporterRunnerPostQueryRepository supporterRunnerPostQueryRepository; + + @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력이 있을 경우 true 를 반환한다.") + @Test + void existsByRunnerPostIdAndMemberId_return_true() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + final SupporterRunnerPost supporterRunnerPost = persistApplicant(supporter, runnerPost); + + supporterRunnerPostQueryRepository.save(supporterRunnerPost); + + // when + final boolean isApplicantHistoryExist = supporterRunnerPostQueryRepository.existsByRunnerPostIdAndMemberId( + runnerPost.getId(), + supporter.getMember().getId() + ); + + // then + assertThat(isApplicantHistoryExist).isTrue(); + } + + @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력이 없을 경우 false 를 반환한다.") + @Test + void existsByRunnerPostIdAndMemberId_if_supporterRunnerPost_is_not_exist_then_return_false() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter supporter = persistSupporter(MemberFixture.createEthan()); + + // when + final boolean isApplicantHistoryNotExist = supporterRunnerPostQueryRepository.existsByRunnerPostIdAndMemberId( + runnerPost.getId(), + supporter.getMember().getId() + ); + + // then + assertThat(isApplicantHistoryNotExist).isFalse(); + } + + @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력을 조회할 때 RunnerPost 자체가 없으면 false 를 반환한다.") + @Test + void existsByRunnerPostIdAndMemberId_if_runnerPost_is_not_exist_then_return_false() { + // given + final Supporter supporter = persistSupporter(MemberFixture.createDitoo()); + + // when + final Long notExistRunnerPostId = -1L; + final boolean isApplicantHistoryNotExist = supporterRunnerPostQueryRepository.existsByRunnerPostIdAndMemberId( + notExistRunnerPostId, + supporter.getMember().getId() + ); + + // then + assertThat(isApplicantHistoryNotExist).isFalse(); + } + + @DisplayName("RunnerPostId 로 SupporterRunnerPost 목록을 조회한다.") + @Test + void readByRunnerPostId() { + // given + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter supporterDitoo = persistSupporter(MemberFixture.createDitoo()); + final SupporterRunnerPost supporterDitooRunnerPost = persistApplicant(supporterDitoo, runnerPost); + final Supporter supporterEthan = persistSupporter(MemberFixture.createEthan()); + final SupporterRunnerPost supporterEthanRunnerPost = persistApplicant(supporterEthan, runnerPost); + + // when + final List actual = supporterRunnerPostQueryRepository.readByRunnerPostId(runnerPost.getId()); + + // then + assertThat(actual).containsExactly(supporterDitooRunnerPost, supporterEthanRunnerPost); + } + + @DisplayName("SupporterId 와 Not Started 인 ReviewStatus 로 RunnerPost 개수를 센다.") + @Test + void countRunnerPostBySupporterIdByReviewStatusNotStarted() { + // given + final Runner runner = persistRunner(MemberFixture.createHyena()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Supporter supporter = persistSupporter(MemberFixture.createDitoo()); + persistApplicant(supporter, runnerPost); + + // when + final long count = supporterRunnerPostQueryRepository.countRunnerPostBySupporterIdByReviewStatusNotStarted(supporter.getId()); + + // then + assertThat(count).isEqualTo(1); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/query/service/SupporterQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/query/service/SupporterQueryServiceTest.java new file mode 100644 index 000000000..1d1b0aead --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/supporter/query/service/SupporterQueryServiceTest.java @@ -0,0 +1,45 @@ +package touch.baton.domain.supporter.query.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.query.service.SupporterQueryService; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.SupporterFixture; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + + +class SupporterQueryServiceTest extends ServiceTestConfig { + + private SupporterQueryService supporterQueryService; + + @BeforeEach + void setUp() { + supporterQueryService = new SupporterQueryService(supporterQueryRepository); + } + + @DisplayName("Supporter 식별자 값으로 Member 패치 조인하여 Supporter 를 조회한다.") + @Test + void readBySupporterId() { + // given + final Member savedMember = memberCommandRepository.save(MemberFixture.createHyena()); + final Supporter savedSupporter = supporterQueryRepository.save(SupporterFixture.create(savedMember)); + + // when + final Supporter foundSupporter = supporterQueryService.readBySupporterId(savedSupporter.getId()); + + // then + assertAll( + () -> assertThat(foundSupporter.getId()).isEqualTo(savedSupporter.getId()), + () -> assertThat(foundSupporter.getIntroduction()).isEqualTo(savedSupporter.getIntroduction()), + () -> assertThat(foundSupporter.getReviewCount()).isEqualTo(savedSupporter.getReviewCount()), + () -> assertThat(foundSupporter.getSupporterTechnicalTags()).isEqualTo(savedSupporter.getSupporterTechnicalTags()), + () -> assertThat(foundSupporter.getMember()).isEqualTo(savedMember) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java deleted file mode 100644 index 7dca5324d..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/repository/SupporterRunnerPostRepositoryTest.java +++ /dev/null @@ -1,209 +0,0 @@ -package touch.baton.domain.supporter.repository; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.RunnerPostRepository; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; -import touch.baton.domain.supporter.vo.Message; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RunnerFixture; -import touch.baton.fixture.domain.RunnerPostFixture; -import touch.baton.fixture.domain.SupporterFixture; -import touch.baton.fixture.domain.SupporterRunnerPostFixture; - -import java.time.LocalDateTime; -import java.util.List; - -import static java.time.LocalDateTime.now; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static touch.baton.fixture.vo.DeadlineFixture.deadline; - -class SupporterRunnerPostRepositoryTest extends RepositoryTestConfig { - - @Autowired - private SupporterRunnerPostRepository supporterRunnerPostRepository; - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private RunnerRepository runnerRepository; - - @Autowired - private SupporterRepository supporterRepository; - - @Autowired - private RunnerPostRepository runnerPostRepository; - - @DisplayName("러너 게시글 식별자값으로 서포터가 지원한 수를 count 한다.") - @Test - void countByRunnerPostIdIn() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createDitoo()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost savedRunnerPostOne = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - final RunnerPost savedRunnerPostTwo = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - final RunnerPost savedRunnerPostThree = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - final RunnerPost savedRunnerPostFour = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - - savedRunnerPostOne.assignSupporter(savedSupporterHyena); - savedRunnerPostTwo.assignSupporter(savedSupporterHyena); - savedRunnerPostThree.assignSupporter(savedSupporterHyena); - savedRunnerPostFour.assignSupporter(savedSupporterHyena); - - supporterRunnerPostRepository.save(createSupporterRunnerPost(savedSupporterHyena, savedRunnerPostOne)); - supporterRunnerPostRepository.save(createSupporterRunnerPost(savedSupporterHyena, savedRunnerPostTwo)); - supporterRunnerPostRepository.save(createSupporterRunnerPost(savedSupporterHyena, savedRunnerPostThree)); - supporterRunnerPostRepository.save(createSupporterRunnerPost(savedSupporterHyena, savedRunnerPostFour)); - - // when - final List runnerPostIds = List.of( - savedRunnerPostOne.getId(), - savedRunnerPostTwo.getId(), - savedRunnerPostThree.getId(), - savedRunnerPostFour.getId() - ); - final List foundRunnerPostsApplicantCounts = supporterRunnerPostRepository.countByRunnerPostIdIn(runnerPostIds); - - // then - assertThat(foundRunnerPostsApplicantCounts).containsExactly(1L, 1L, 1L, 1L); - } - - private SupporterRunnerPost createSupporterRunnerPost(final Supporter supporter, final RunnerPost runnerPost) { - return SupporterRunnerPost.builder() - .runnerPost(runnerPost) - .supporter(supporter) - .message(new Message("안녕하세요. 서포터 헤나입니다.")) - .build(); - } - - @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력이 있을 경우 true 를 반환한다.") - @Test - void existsByRunnerPostIdAndMemberId_return_true() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - - final SupporterRunnerPost runnerPostApplicant = createSupporterRunnerPost(savedSupporterHyena, savedRunnerPost); - supporterRunnerPostRepository.save(runnerPostApplicant); - - // when - final Long notExistMemberId = -1L; - final boolean isApplicantHistoryExist = supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId( - savedRunnerPost.getId(), - savedMemberHyena.getId() - ); - - // then - assertThat(isApplicantHistoryExist).isTrue(); - } - - @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력이 없을 경우 false 를 반환한다.") - @Test - void existsByRunnerPostIdAndMemberId_if_supporterRunnerPost_is_not_exist_then_return_false() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createHyena()); - - final RunnerPost savedRunnerPost = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - - // when - final boolean isApplicantHistoryNotExist = supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId( - savedRunnerPost.getId(), - savedMemberHyena.getId() - ); - - // then - assertThat(isApplicantHistoryNotExist).isFalse(); - } - - @DisplayName("Member 가 SupporterRunnerPost 에 지원한 이력을 조회할 때 RunnerPost 자체가 없으면 false 를 반환한다.") - @Test - void existsByRunnerPostIdAndMemberId_if_runnerPost_is_not_exist_then_return_false() { - // given - final Member savedMemberHyena = memberRepository.save(MemberFixture.createDitoo()); - - // when - final Long notExistRunnerPostId = -1L; - final boolean isApplicantHistoryNotExist = supporterRunnerPostRepository.existsByRunnerPostIdAndMemberId( - notExistRunnerPostId, - savedMemberHyena.getId() - ); - - // then - assertThat(isApplicantHistoryNotExist).isFalse(); - } - - @DisplayName("RunnerPost 외래키로 된 SupporterRunnerPost 가 존재하는지 확인한다.") - @Test - void existsByRunnerPostId() { - // given - final Member savedMemberDitoo = memberRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - - final Member savedMemberHyena = memberRepository.save(MemberFixture.createDitoo()); - final Supporter savedSupporterHyena = supporterRepository.save(SupporterFixture.create(savedMemberHyena)); - - final RunnerPost runnerPostOfApplicantExist = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - final RunnerPost runnerPostOfApplicantNotExist = runnerPostRepository.save(RunnerPostFixture.create(savedRunnerDitoo, new Deadline(now().plusHours(100)))); - runnerPostOfApplicantExist.assignSupporter(savedSupporterHyena); - supporterRunnerPostRepository.save(createSupporterRunnerPost(savedSupporterHyena, runnerPostOfApplicantExist)); - - // when - final boolean actualOfExist = supporterRunnerPostRepository.existsByRunnerPostId(runnerPostOfApplicantExist.getId()); - final boolean actualOfNotExist = supporterRunnerPostRepository.existsByRunnerPostId(runnerPostOfApplicantNotExist.getId()); - - // then - assertSoftly(softly -> { - softly.assertThat(actualOfExist).isTrue(); - softly.assertThat(actualOfNotExist).isFalse(); - }); - } - - @DisplayName("서포터의 러너 게시글 리뷰 제안을 철회하는데 성공한다") - @Test - void deleteBySupporterAndRunnerPostId() { - // given - final Member reviewerMember = memberRepository.save(MemberFixture.createDitoo()); - final Supporter reviewerSupporter = supporterRepository.save(SupporterFixture.create(reviewerMember)); - - final Member revieweeMember = memberRepository.save(MemberFixture.createJudy()); - final Runner revieweeRunner = runnerRepository.save(RunnerFixture.createRunner(revieweeMember)); - - final RunnerPost runnerPost = runnerPostRepository.save(RunnerPostFixture.create( - revieweeRunner, - reviewerSupporter, - deadline(LocalDateTime.now().plusHours(100)) - )); - - final SupporterRunnerPost deletedSupporterRunnerPost = supporterRunnerPostRepository.save( - SupporterRunnerPostFixture.create(runnerPost, reviewerSupporter)); - - // when - supporterRunnerPostRepository.deleteBySupporterIdAndRunnerPostId(reviewerSupporter.getId(), runnerPost.getId()); - - // then - assertThat(supporterRunnerPostRepository.findById(deletedSupporterRunnerPost.getId())).isNotPresent(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java deleted file mode 100644 index 0c22dbed7..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/supporter/service/SupporterServiceTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package touch.baton.domain.supporter.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.service.dto.SupporterUpdateRequest; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.SupporterFixture; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - - -class SupporterServiceTest extends ServiceTestConfig { - - private SupporterService supporterService; - - @BeforeEach - void setUp() { - supporterService = new SupporterService(supporterRepository, technicalTagRepository, supporterTechnicalTagRepository); - } - - @DisplayName("Supporter 식별자 값으로 Member 패치 조인하여 Supporter 를 조회한다.") - @Test - void readBySupporterId() { - // given - final Member savedMember = memberRepository.save(MemberFixture.createHyena()); - final Supporter savedSupporter = supporterRepository.save(SupporterFixture.create(savedMember)); - - // when - final Supporter foundSupporter = supporterService.readBySupporterId(savedSupporter.getId()); - - // then - assertAll( - () -> assertThat(foundSupporter.getId()).isEqualTo(savedSupporter.getId()), - () -> assertThat(foundSupporter.getIntroduction()).isEqualTo(savedSupporter.getIntroduction()), - () -> assertThat(foundSupporter.getReviewCount()).isEqualTo(savedSupporter.getReviewCount()), - () -> assertThat(foundSupporter.getSupporterTechnicalTags()).isEqualTo(savedSupporter.getSupporterTechnicalTags()), - () -> assertThat(foundSupporter.getMember()).isEqualTo(savedMember) - ); - } - - @DisplayName("Supporter 정보를 수정한다.") - @Test - void updateSupporter() { - // given - final Member savedMember = memberRepository.save(MemberFixture.createDitoo()); - final Supporter savedSupporter = supporterRepository.save(SupporterFixture.create(savedMember)); - final SupporterUpdateRequest request = new SupporterUpdateRequest("디투랜드", "두나무", "소개글입니다.", List.of("golang", "rust")); - - // when & then - assertThatCode(() -> supporterService.updateSupporter(savedSupporter, request)) - .doesNotThrowAnyException(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/RunnerPostTagTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/command/RunnerPostTagTest.java similarity index 76% rename from backend/baton/src/test/java/touch/baton/domain/tag/RunnerPostTagTest.java rename to backend/baton/src/test/java/touch/baton/domain/tag/command/RunnerPostTagTest.java index 95eb857b2..3742f29ae 100644 --- a/backend/baton/src/test/java/touch/baton/domain/tag/RunnerPostTagTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/tag/command/RunnerPostTagTest.java @@ -1,30 +1,30 @@ -package touch.baton.domain.tag; +package touch.baton.domain.tag.command; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.vo.ReviewCount; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.command.vo.Title; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; import touch.baton.domain.tag.exception.RunnerPostTagDomainException; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; import java.time.LocalDateTime; diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/RunnerPostTagsTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/command/RunnerPostTagsTest.java similarity index 73% rename from backend/baton/src/test/java/touch/baton/domain/tag/RunnerPostTagsTest.java rename to backend/baton/src/test/java/touch/baton/domain/tag/command/RunnerPostTagsTest.java index e7010dba9..598c3ee51 100644 --- a/backend/baton/src/test/java/touch/baton/domain/tag/RunnerPostTagsTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/tag/command/RunnerPostTagsTest.java @@ -1,16 +1,16 @@ -package touch.baton.domain.tag; +package touch.baton.domain.tag.command; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; import java.time.LocalDateTime; @@ -25,7 +25,7 @@ class RunnerPostTagsTest { @Test void addAllRunnerPostTags() { // given - RunnerPostTags postTags = new RunnerPostTags(); + RunnerPostTags postTags = new RunnerPostTags(new ArrayList<>()); Member member = Member.builder() .memberName(new MemberName("러너 사용자")) .socialId(new SocialId("testSocialId")) diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/TagTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/command/TagTest.java similarity index 91% rename from backend/baton/src/test/java/touch/baton/domain/tag/TagTest.java rename to backend/baton/src/test/java/touch/baton/domain/tag/command/TagTest.java index 558a8efd8..f2bec9b19 100644 --- a/backend/baton/src/test/java/touch/baton/domain/tag/TagTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/tag/command/TagTest.java @@ -1,11 +1,12 @@ -package touch.baton.domain.tag; +package touch.baton.domain.tag.command; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; import touch.baton.domain.tag.exception.TagDomainException; -import touch.baton.domain.tag.vo.TagReducedName; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/vo/TagReducedNameTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/command/vo/TagReducedNameTest.java similarity index 97% rename from backend/baton/src/test/java/touch/baton/domain/tag/vo/TagReducedNameTest.java rename to backend/baton/src/test/java/touch/baton/domain/tag/command/vo/TagReducedNameTest.java index 9cf64d9ca..a26810c05 100644 --- a/backend/baton/src/test/java/touch/baton/domain/tag/vo/TagReducedNameTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/tag/command/vo/TagReducedNameTest.java @@ -1,4 +1,4 @@ -package touch.baton.domain.tag.vo; +package touch.baton.domain.tag.command.vo; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/query/repository/RunnerPostTagQueryRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/query/repository/RunnerPostTagQueryRepositoryTest.java new file mode 100644 index 000000000..ba2e2837d --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/tag/query/repository/RunnerPostTagQueryRepositoryTest.java @@ -0,0 +1,57 @@ +package touch.baton.domain.tag.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.Tag; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class RunnerPostTagQueryRepositoryTest extends RepositoryTestConfig { + + @Autowired + private RunnerPostTagQueryRepository runnerPostTagQueryRepository; + + @DisplayName("RunnerPostTag 의 식별자값 목록으로 Tag 목록을 조회한다.") + @Test + void success_joinTagByRunnerPostId() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final RunnerPost runnerPost = persistRunnerPost(runner); + final Tag tag = persistTag("java"); + final RunnerPostTag runnerPostTag = persistRunnerPostTag(runnerPost, tag); + + em.flush(); + em.close(); + + // when + final List joinRunnerPostTags = runnerPostTagQueryRepository.joinTagByRunnerPostId(runnerPost.getId()); + + // then + assertThat(joinRunnerPostTags).containsExactly(runnerPostTag); + } + + @DisplayName("RunnerPostTag 의 식별자값 목록이 비어있을 때 빈 컬렉션을 반환한다.") + @Test + void success_joinTagByRunnerPostIds_if_tag_is_empty() { + // given + final Runner runner = persistRunner(MemberFixture.createDitoo()); + final RunnerPost runnerPost = persistRunnerPost(runner); + + em.flush(); + em.close(); + + // when + final List joinRunnerPostTags = runnerPostTagQueryRepository.joinTagByRunnerPostId(runnerPost.getId()); + + // then + assertThat(joinRunnerPostTags).isEmpty(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/query/repository/TagQuerydslRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/query/repository/TagQuerydslRepositoryTest.java new file mode 100644 index 000000000..19c05e202 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/tag/query/repository/TagQuerydslRepositoryTest.java @@ -0,0 +1,103 @@ +package touch.baton.domain.tag.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.common.vo.TagName; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class TagQuerydslRepositoryTest extends RepositoryTestConfig { + + @Autowired + private TagQuerydslRepository tagQuerydslRepository; + + @DisplayName("축약된 태그 이름으로 태그 목록을 limit 개 축약된 태그 이름 기준 오름차순으로 검색한다.") + @Test + void readTagsByTagReducedName() { + // given + for (int saveTagCount = 1; saveTagCount <= 3; saveTagCount++) { + persistTag("java" + saveTagCount); + persistTag("javascript" + saveTagCount); + persistTag("assertj" + saveTagCount); + } + for (int saveTagCount = 4; saveTagCount <= 6; saveTagCount++) { + persistTag("j ava" + saveTagCount); + persistTag("j avascript" + saveTagCount); + persistTag("a ssertj" + saveTagCount); + } + for (int saveTagCount = 7; saveTagCount <= 10; saveTagCount++) { + persistTag("ja va" + saveTagCount); + persistTag("ja vascript" + saveTagCount); + persistTag("as sertj" + saveTagCount); + } + + em.flush(); + em.close(); + + // when + final List actual = tagQuerydslRepository.findByTagReducedName(TagReducedName.from("j"), 10); + + // then + final List actualSortedTagNames = actual.stream() + .map(Tag::getTagName) + .map(TagName::getValue) + .toList(); + + assertThat(actualSortedTagNames).containsExactly( + "java1", "ja va10", "java2", "java3", "j ava4", "j ava5", "j ava6", "ja va7", "ja va8", "ja va9" + ); + } + + @DisplayName("입력된 TagReducedName 으로 시작하는 태그만 검색한다") + @Test + void success_readTagsByReducedName_when_name_isNotMatched_atAll() { + // given + persistTag("assertj"); + + em.flush(); + em.close(); + + // when + final TagReducedName tagReducedName = TagReducedName.nullableInstance("j"); + final List actual = tagQuerydslRepository.findByTagReducedName(tagReducedName, 10); + + // then + assertThat(actual.isEmpty()).isTrue(); + } + + @DisplayName("입력된 TagReducedName 으로 시작하는 이름을 갖는 태그가 없다면 빈 목록을 반환한다.") + @Test + void success_readTagsByReducedName_when_foundTags_isEmpty() { + // given + persistTag("aaaaaaaaa"); + + em.flush(); + em.close(); + + // when + final TagReducedName tagReducedName = TagReducedName.nullableInstance("b"); + final List actual = tagQuerydslRepository.findByTagReducedName(tagReducedName, 10); + + // then + assertThat(actual.isEmpty()).isTrue(); + } + + @DisplayName("TagReducedName 내부 값이 blank 인 경우 빈 목록을 반환한다.") + @Test + void success_readTagsByReducedName_when_tagReducedNameIsBlank() { + // given + final TagReducedName tagReducedName = TagReducedName.nullableInstance(""); + + // when + final List actual = tagQuerydslRepository.findByTagReducedName(tagReducedName, 10); + + // then + assertThat(actual.isEmpty()).isTrue(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/query/service/TagQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/query/service/TagQueryServiceTest.java new file mode 100644 index 000000000..eba2b23bd --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/tag/query/service/TagQueryServiceTest.java @@ -0,0 +1,77 @@ +package touch.baton.domain.tag.query.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; +import touch.baton.fixture.domain.TagFixture; +import touch.baton.fixture.vo.TagNameFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +class TagQueryServiceTest extends ServiceTestConfig { + + private TagQueryService tagQueryService; + + @BeforeEach + void setUp() { + tagQueryService = new TagQueryService(tagQuerydslRepository); + } + + @DisplayName("Tag의 이름으로 Tag를 오름차순으로 10개 조회한다.") + @Test + void success_readTagsByReducedName() { + // given + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ja va"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("jav a1"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("j ava2"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ja va3"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("jav a4"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("java 5"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("j ava6"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ja va7"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("jav a8"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("java 9"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ju ja"))); + + // when + final TagReducedName tagReducedName = TagReducedName.nullableInstance("j a"); + final List actual = tagQueryService.readTagsByReducedName(tagReducedName, 10); + + // then + assertSoftly(softly -> { + softly.assertThat(actual).hasSize(10); + softly.assertThat(actual.get(0).getTagName().getValue()).isEqualTo("ja va"); + softly.assertThat(actual.get(9).getTagName().getValue()).isEqualTo("java 9"); + }); + } + + @DisplayName("TagReducedName 이 null 인 경우 빈 목록을 반환한다.") + @Test + void success_when_TagReducedName_IsNull() { + // given + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ja va"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("jav a1"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("j ava2"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ja va3"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("jav a4"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("java 5"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("j ava6"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ja va7"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("jav a8"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("java 9"))); + tagCommandRepository.save(TagFixture.create(TagNameFixture.tagName("ju ja"))); + + // when + final TagReducedName nullTagReducedName = null; + final List actual = tagQueryService.readTagsByReducedName(nullTagReducedName, 10); + + // then + assertThat(actual).isEmpty(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/repository/RunnerPostTagRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/repository/RunnerPostTagRepositoryTest.java deleted file mode 100644 index a99ecb85c..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/tag/repository/RunnerPostTagRepositoryTest.java +++ /dev/null @@ -1,160 +0,0 @@ -package touch.baton.domain.tag.repository; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.repository.MemberRepository; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runner.repository.RunnerRepository; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.repository.RunnerPostRepository; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.RunnerPostTags; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.vo.TagReducedName; -import touch.baton.fixture.domain.RunnerTechnicalTagsFixture; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class RunnerPostTagRepositoryTest extends RepositoryTestConfig { - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private RunnerRepository runnerRepository; - - @Autowired - private RunnerPostRepository runnerPostRepository; - - @Autowired - private RunnerPostTagRepository runnerPostTagRepository; - - @Autowired - private TagRepository tagRepository; - - @DisplayName("RunnerPostTag 의 식별자값 목록으로 Tag 목록을 조회한다.") - @Test - void success_joinTagByRunnerPostIds() { - // given - final Member member = Member.builder() - .memberName(new MemberName("헤에디주")) - .socialId(new SocialId("testSocialId")) - .oauthId(new OauthId("dsigjh98gh230gn2oinv913bcuo23nqovbvu93b12voi3bc31j")) - .githubUrl(new GithubUrl("github.com/hyena0608")) - .company(new Company("우아한형제들")) - .imageUrl(new ImageUrl("김석호")) - .build(); - final Member saveMember = memberRepository.saveAndFlush(member); - - final Runner runner = Runner.builder() - .member(saveMember) - .runnerTechnicalTags(RunnerTechnicalTagsFixture.create(new ArrayList<>())) - .build(); - final Runner saveRunner = runnerRepository.saveAndFlush(runner); - - final LocalDateTime deadline = LocalDateTime.now(); - final RunnerPost runnerPost = RunnerPost.builder() - .title(new Title("제 코드 리뷰 좀 해주세요!!")) - .implementedContents(new ImplementedContents("제 코드는 클린코드가 맞을까요?")) - .curiousContents(new CuriousContents("저는 클린코드가 궁금해요.")) - .postscriptContents(new PostscriptContents("저 상처 잘 받으니깐 부드럽게 말해주세요.")) - .deadline(new Deadline(deadline)) - .pullRequestUrl(new PullRequestUrl("https://")) - .watchedCount(new WatchedCount(1)) - .runnerPostTags(new RunnerPostTags(new ArrayList<>())) - .reviewStatus(ReviewStatus.NOT_STARTED) - .isReviewed(IsReviewed.notReviewed()) - .runner(saveRunner) - .supporter(null) - .build(); - runnerPostRepository.saveAndFlush(runnerPost); - - final Tag tag = Tag.builder() - .tagName(new TagName("자바")) - .tagReducedName(TagReducedName.from("자바")) - .build(); - tagRepository.save(tag); - - final RunnerPostTag runnerPostTag = RunnerPostTag.builder() - .runnerPost(runnerPost) - .tag(tag) - .build(); - runnerPostTagRepository.save(runnerPostTag); - - // when - final List joinRunnerPostTags - = runnerPostTagRepository.joinTagByRunnerPostId(runnerPost.getId()); - - // then - assertThat(joinRunnerPostTags).containsExactly(runnerPostTag); - } - - @DisplayName("RunnerPostTag 의 식별자값 목록이 비어있을 때 빈 컬렉션을 반환한다.") - @Test - void success_joinTagByRunnerPostIds_if_tag_is_empty() { - // given - final Member member = Member.builder() - .memberName(new MemberName("헤에디주")) - .socialId(new SocialId("testSocialId")) - .oauthId(new OauthId("dsigjh98gh230gn2oinv913bcuo23nqovbvu93b12voi3bc31j")) - .githubUrl(new GithubUrl("github.com/hyena0608")) - .company(new Company("우아한형제들")) - .imageUrl(new ImageUrl("김석호")) - .build(); - final Member saveMember = memberRepository.saveAndFlush(member); - - final Runner runner = Runner.builder() - .member(saveMember) - .runnerTechnicalTags(RunnerTechnicalTagsFixture.create(new ArrayList<>())) - .build(); - - final Runner saveRunner = runnerRepository.saveAndFlush(runner); - - final LocalDateTime deadline = LocalDateTime.now(); - final RunnerPost runnerPost = RunnerPost.builder() - .title(new Title("제 코드 리뷰 좀 해주세요!!")) - .implementedContents(new ImplementedContents("제 코드는 클린코드가 맞을까요?")) - .curiousContents(new CuriousContents("저는 클린코드가 궁금해요.")) - .postscriptContents(new PostscriptContents("저 상처 잘 받으니깐 부드럽게 말해주세요.")) - .deadline(new Deadline(deadline)) - .pullRequestUrl(new PullRequestUrl("https://")) - .watchedCount(new WatchedCount(1)) - .runnerPostTags(new RunnerPostTags(new ArrayList<>())) - .reviewStatus(ReviewStatus.NOT_STARTED) - .isReviewed(IsReviewed.notReviewed()) - .runner(saveRunner) - .supporter(null) - .build(); - runnerPostRepository.saveAndFlush(runnerPost); - - // when - final List joinRunnerPostTags - = runnerPostTagRepository.joinTagByRunnerPostId(runnerPost.getId()); - - // then - assertThat(joinRunnerPostTags).isEmpty(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/repository/TagRepositoryReadTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/repository/TagRepositoryReadTest.java deleted file mode 100644 index d3bcc18cb..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/tag/repository/TagRepositoryReadTest.java +++ /dev/null @@ -1,116 +0,0 @@ -package touch.baton.domain.tag.repository; - -import jakarta.persistence.EntityManager; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.vo.TagReducedName; -import touch.baton.fixture.domain.TagFixture; -import touch.baton.fixture.vo.TagNameFixture; - -import java.util.List; -import java.util.Optional; -import java.util.stream.IntStream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; - -class TagRepositoryReadTest extends RepositoryTestConfig { - - @Autowired - private TagRepository tagRepository; - - @Autowired - private EntityManager em; - - @DisplayName("이름으로 단건 검색한다.") - @Test - void findByName() { - // given - final Tag javaTag = persistTag("java"); - final Tag uppercaseJavaTag = persistTag("Java"); - - em.flush(); - em.close(); - - // when - final Optional actual = tagRepository.findByTagName(TagNameFixture.tagName("java")); - - // then - assertThat(actual).contains(javaTag); - } - - @DisplayName("이름을 오름차순으로 10개 검색한다.") - @Test - void success_readTagsByReducedName() { - // given - persistTag("ja va"); - persistTag("j ava1"); - persistTag("ja va2"); - persistTag("jav a3"); - persistTag("java 4"); - persistTag("ja va5"); - persistTag("j ava6"); - persistTag("ja va7"); - persistTag("jav a8"); - persistTag("java 9"); - persistTag("assert ja"); - - em.flush(); - em.close(); - - // when - final TagReducedName reducedName = TagReducedName.from("ja"); - final List actual = tagRepository.readTagsByReducedName(reducedName); - - // then - assertSoftly(softly -> { - softly.assertThat(actual).hasSize(10); - softly.assertThat(actual.get(0).getTagName().getValue()).isEqualTo("ja va"); - softly.assertThat(actual.get(9).getTagName().getValue()).isEqualTo("java 9"); - }); - - } - - @DisplayName("이름으로 시작하는 태그만 검색한다") - @Test - void fail_readTagsByReducedName() { - // given - final Tag tag = TagFixture.create(TagNameFixture.tagName("assertj")); - final TagReducedName reducedName = TagReducedName.from("j"); - tagRepository.save(tag); - - // when - final List actual = tagRepository.readTagsByReducedName(reducedName); - - // then - assertThat(actual.isEmpty()).isTrue(); - } - - @DisplayName("이름에 해당하는 태그가 없다면 빈 배열을 반환한다.") - @Test - void success_readTagsByReducedName_when_no_match_tag() { - // given - final TagReducedName reducedName = TagReducedName.from("hi"); - persistTag("hellohi"); - - em.flush(); - em.close(); - - // when - final List actual = tagRepository.readTagsByReducedName(reducedName); - - // then - assertThat(actual.isEmpty()).isTrue(); - } - - private Tag persistTag(final String tagName) { - final Tag tag = TagFixture.create(TagNameFixture.tagName(tagName)); - em.persist(tag); - - return tag; - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/tag/service/TagServiceReadTest.java b/backend/baton/src/test/java/touch/baton/domain/tag/service/TagServiceReadTest.java deleted file mode 100644 index 45b957299..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/tag/service/TagServiceReadTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package touch.baton.domain.tag.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import touch.baton.config.ServiceTestConfig; -import touch.baton.domain.tag.Tag; -import touch.baton.fixture.domain.TagFixture; -import touch.baton.fixture.vo.TagNameFixture; - -import java.util.List; -import java.util.stream.IntStream; - -import static org.assertj.core.api.SoftAssertions.assertSoftly; - -class TagServiceReadTest extends ServiceTestConfig { - - private TagService tagService; - - @BeforeEach - void setUp() { - tagService = new TagService(tagRepository); - } - - @DisplayName("Tag의 이름으로 Tag를 오름차순으로 10개 조회한다.") - @Test - void success_readTagsByReducedName() { - // given - tagRepository.save(TagFixture.create(TagNameFixture.tagName("ja va"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("jav a1"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("j ava2"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("ja va3"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("jav a4"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("java 5"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("j ava6"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("ja va7"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("jav a8"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("java 9"))); - tagRepository.save(TagFixture.create(TagNameFixture.tagName("ju ja"))); - - // when - final List actual = tagService.readTagsByReducedName("j a"); - - // then - assertSoftly(softly -> { - softly.assertThat(actual).hasSize(10); - softly.assertThat(actual.get(0).getTagName().getValue()).isEqualTo("ja va"); - softly.assertThat(actual.get(9).getTagName().getValue()).isEqualTo("java 9"); - }); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagTest.java index 933e8b5f1..be2e1d1c5 100644 --- a/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagTest.java @@ -3,10 +3,12 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import touch.baton.domain.member.Member; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.vo.ReviewCount; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; import touch.baton.domain.tag.exception.SupporterTechnicalTagDomainException; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.TechnicalTagFixture; diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java index cedbf2dc4..d1a79e93b 100644 --- a/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/SupporterTechnicalTagsTest.java @@ -3,13 +3,17 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import touch.baton.domain.member.Member; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.SupporterTechnicalTagFixture; import touch.baton.fixture.domain.TechnicalTagFixture; +import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +35,7 @@ void setUp() { @Test void addAll() { // given - final SupporterTechnicalTags supporterTechnicalTags = new SupporterTechnicalTags(); + final SupporterTechnicalTags supporterTechnicalTags = new SupporterTechnicalTags(new ArrayList<>()); // when supporterTechnicalTags.addAll(List.of(supporterTechnicalTag)); diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/TechnicalTagTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/TechnicalTagTest.java index e7f77a11f..5c65fdf95 100644 --- a/backend/baton/src/test/java/touch/baton/domain/technicaltag/TechnicalTagTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/TechnicalTagTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import touch.baton.domain.common.vo.TagName; import touch.baton.domain.tag.exception.TechnicalTagDomainException; +import touch.baton.domain.technicaltag.command.TechnicalTag; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagQueryRepositoryTest.java similarity index 75% rename from backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagQueryRepositoryTest.java index 6f9d21652..ff06640ed 100644 --- a/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/SupporterTechnicalTagQueryRepositoryTest.java @@ -5,10 +5,11 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.Member; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.TechnicalTag; +import touch.baton.domain.technicaltag.command.repository.SupporterTechnicalTagCommandRepository; import touch.baton.fixture.domain.MemberFixture; import touch.baton.fixture.domain.SupporterFixture; import touch.baton.fixture.domain.SupporterTechnicalTagFixture; @@ -18,10 +19,10 @@ import static org.assertj.core.api.Assertions.assertThat; -class SupporterTechnicalTagRepositoryTest extends RepositoryTestConfig { +class SupporterTechnicalTagQueryRepositoryTest extends RepositoryTestConfig { @Autowired - private SupporterTechnicalTagRepository supporterTechnicalTagRepository; + private SupporterTechnicalTagCommandRepository supporterTechnicalTagCommandRepository; @Autowired private EntityManager em; @@ -44,13 +45,13 @@ void deleteBySupporter() { final SupporterTechnicalTag supporterTechnicalTag2 = SupporterTechnicalTagFixture.create(supporter, technicalTag2); final SupporterTechnicalTag supporterTechnicalTag3 = SupporterTechnicalTagFixture.create(supporter, technicalTag3); final List savedSupporterTechnicalTags = List.of(supporterTechnicalTag1, supporterTechnicalTag2, supporterTechnicalTag3); - supporterTechnicalTagRepository.saveAll(savedSupporterTechnicalTags); + supporterTechnicalTagCommandRepository.saveAll(savedSupporterTechnicalTags); em.flush(); em.close(); // when final int expected = savedSupporterTechnicalTags.size(); - final int actual = supporterTechnicalTagRepository.deleteBySupporter(supporter); + final int actual = supporterTechnicalTagCommandRepository.deleteBySupporter(supporter); // then assertThat(expected).isEqualTo(actual); diff --git a/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagQueryRepositoryTest.java similarity index 67% rename from backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepositoryTest.java rename to backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagQueryRepositoryTest.java index 605c2f274..b8089f77b 100644 --- a/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/technicaltag/repository/TechnicalTagQueryRepositoryTest.java @@ -5,7 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.technicaltag.command.TechnicalTag; +import touch.baton.domain.technicaltag.query.repository.TechnicalTagQueryRepository; import touch.baton.fixture.domain.TechnicalTagFixture; import touch.baton.fixture.vo.TagNameFixture; @@ -14,21 +15,21 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; -class TechnicalTagRepositoryTest extends RepositoryTestConfig { +class TechnicalTagQueryRepositoryTest extends RepositoryTestConfig { @Autowired - private TechnicalTagRepository technicalTagRepository; + private TechnicalTagQueryRepository technicalTagQueryRepository; @DisplayName("TagName 으로 TechnicalTag 를 검색할 때 TechnicalTag 가 존재하면 검색된다.") @Test void findByName_ifPresent() { // given final TagName tagName = TagNameFixture.tagName("java"); - final TechnicalTag expected = technicalTagRepository.save(TechnicalTagFixture.create(tagName)); - technicalTagRepository.flush(); + final TechnicalTag expected = technicalTagQueryRepository.save(TechnicalTagFixture.create(tagName)); + technicalTagQueryRepository.flush(); // when - final Optional actual = technicalTagRepository.findByTagName(tagName); + final Optional actual = technicalTagQueryRepository.findByTagName(tagName); // then assertSoftly(softly -> { @@ -44,7 +45,7 @@ void findByName_ifNotPresent() { final TagName tagName = new TagName("java"); // when - final Optional actual = technicalTagRepository.findByTagName(tagName); + final Optional actual = technicalTagQueryRepository.findByTagName(tagName); // then assertThat(actual).isNotPresent(); diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java index c5c8bb076..feeeec77c 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java @@ -1,12 +1,12 @@ package touch.baton.fixture.domain; -import touch.baton.domain.member.Member; -import touch.baton.domain.member.vo.Company; -import touch.baton.domain.member.vo.GithubUrl; -import touch.baton.domain.member.vo.ImageUrl; -import touch.baton.domain.member.vo.MemberName; -import touch.baton.domain.member.vo.OauthId; -import touch.baton.domain.member.vo.SocialId; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.Company; +import touch.baton.domain.member.command.vo.GithubUrl; +import touch.baton.domain.member.command.vo.ImageUrl; +import touch.baton.domain.member.command.vo.MemberName; +import touch.baton.domain.member.command.vo.OauthId; +import touch.baton.domain.member.command.vo.SocialId; import static touch.baton.fixture.vo.CompanyFixture.company; import static touch.baton.fixture.vo.GithubUrlFixture.githubUrl; diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/NotificationFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/NotificationFixture.java new file mode 100644 index 000000000..8b0c84fa5 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/NotificationFixture.java @@ -0,0 +1,27 @@ +package touch.baton.fixture.domain; + +import touch.baton.domain.member.command.Member; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.IsRead; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; + +import static touch.baton.domain.notification.command.vo.NotificationType.RUNNER_POST; +import static touch.baton.fixture.vo.NotificationMessageFixture.notificationMessage; +import static touch.baton.fixture.vo.NotificationTitleFixture.notificationTitle; + +public abstract class NotificationFixture { + + private NotificationFixture() { + } + + public static Notification create(final Member targetMember, final NotificationReferencedId notificationReferencedId) { + return Notification.builder() + .notificationTitle(notificationTitle("테스트용 알림 제목")) + .notificationMessage(notificationMessage("테스트용 알림 내용")) + .notificationType(RUNNER_POST) + .notificationReferencedId(notificationReferencedId) + .isRead(IsRead.asUnRead()) + .member(targetMember) + .build(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java index 466b815fe..b6bedd1b9 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java @@ -1,9 +1,9 @@ package touch.baton.fixture.domain; -import touch.baton.domain.member.Member; -import touch.baton.domain.oauth.token.ExpireDate; -import touch.baton.domain.oauth.token.RefreshToken; -import touch.baton.domain.oauth.token.Token; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.token.ExpireDate; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; public abstract class RefreshTokenFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerFixture.java index c4ddf38a9..8582a582f 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerFixture.java @@ -1,11 +1,11 @@ package touch.baton.fixture.domain; -import touch.baton.domain.common.vo.Introduction; -import touch.baton.domain.member.Member; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.technicaltag.RunnerTechnicalTag; -import touch.baton.domain.technicaltag.RunnerTechnicalTags; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.vo.Introduction; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTag; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTags; +import touch.baton.domain.technicaltag.command.TechnicalTag; import java.util.ArrayList; import java.util.List; diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostFixture.java index bae8fc7c1..59d4cd25f 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostFixture.java @@ -1,20 +1,20 @@ package touch.baton.fixture.domain; -import touch.baton.domain.common.vo.Title; -import touch.baton.domain.common.vo.WatchedCount; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.runnerpost.vo.CuriousContents; -import touch.baton.domain.runnerpost.vo.Deadline; -import touch.baton.domain.runnerpost.vo.ImplementedContents; -import touch.baton.domain.runnerpost.vo.IsReviewed; -import touch.baton.domain.runnerpost.vo.PostscriptContents; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; -import touch.baton.domain.runnerpost.vo.ReviewStatus; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.RunnerPostTags; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.IsReviewed; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.ReviewStatus; +import touch.baton.domain.runnerpost.command.vo.Title; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.RunnerPostTags; +import touch.baton.domain.tag.command.Tag; import touch.baton.fixture.vo.DeadlineFixture; import java.time.LocalDateTime; @@ -93,7 +93,7 @@ public static RunnerPost create(final Runner runner, .build(); } - public static RunnerPost create(final Runner runner, final Deadline deadline, List tags) { + public static RunnerPost create(final Runner runner, final Deadline deadline, final List tags) { final RunnerPost runnerPost = RunnerPost.builder() .title(new Title("테스트 제목")) .implementedContents(new ImplementedContents("테스트 내용")) @@ -118,6 +118,35 @@ public static RunnerPost create(final Runner runner, final Deadline deadline, Li return runnerPost; } + public static RunnerPost create(final Runner runner, + final Deadline deadline, + final List tags, + final ReviewStatus reviewStatus + ) { + final RunnerPost runnerPost = RunnerPost.builder() + .title(new Title("테스트 제목")) + .implementedContents(new ImplementedContents("테스트 내용")) + .curiousContents(new CuriousContents("테스트 궁금 점")) + .postscriptContents(new PostscriptContents("테스트 참고 사항")) + .pullRequestUrl(new PullRequestUrl("https://테스트")) + .deadline(deadline) + .watchedCount(new WatchedCount(0)) + .reviewStatus(reviewStatus) + .isReviewed(IsReviewed.notReviewed()) + .runner(runner) + .supporter(null) + .runnerPostTags(new RunnerPostTags(new ArrayList<>())) + .build(); + + final List runnerPostTags = tags.stream() + .map(tag -> RunnerPostTagFixture.create(runnerPost, tag)) + .toList(); + + runnerPost.addAllRunnerPostTags(runnerPostTags); + + return runnerPost; + } + public static RunnerPost create(final Runner runner, final Supporter supporter) { return RunnerPost.builder() .title(new Title("테스트 제목")) @@ -135,10 +164,10 @@ public static RunnerPost create(final Runner runner, final Supporter supporter) .build(); } - public static RunnerPost createWithReviewStatus(final Runner runner, - final Supporter supporter, - final ReviewStatus reviewStatus, - final IsReviewed isReviewed + public static RunnerPost createWithSupporter(final Runner runner, + final Supporter supporter, + final ReviewStatus reviewStatus, + final IsReviewed isReviewed ) { return RunnerPost.builder() .title(new Title("테스트 제목")) diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagFixture.java index b244ce1bb..0f1b00316 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagFixture.java @@ -1,8 +1,8 @@ package touch.baton.fixture.domain; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.Tag; +import touch.baton.domain.runnerpost.command.RunnerPost; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.Tag; public abstract class RunnerPostTagFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagsFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagsFixture.java index 3a62d7096..0505850bd 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagsFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerPostTagsFixture.java @@ -1,7 +1,7 @@ package touch.baton.fixture.domain; -import touch.baton.domain.tag.RunnerPostTag; -import touch.baton.domain.tag.RunnerPostTags; +import touch.baton.domain.tag.command.RunnerPostTag; +import touch.baton.domain.tag.command.RunnerPostTags; import java.util.List; diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerTechnicalTagsFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerTechnicalTagsFixture.java index 0561a4afc..df1e25c71 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerTechnicalTagsFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/RunnerTechnicalTagsFixture.java @@ -1,9 +1,9 @@ package touch.baton.fixture.domain; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.technicaltag.RunnerTechnicalTag; -import touch.baton.domain.technicaltag.RunnerTechnicalTags; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTag; +import touch.baton.domain.technicaltag.command.RunnerTechnicalTags; +import touch.baton.domain.technicaltag.command.TechnicalTag; import java.util.List; diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFeedbackFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFeedbackFixture.java index 9a4bf0192..0365ef389 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFeedbackFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFeedbackFixture.java @@ -1,11 +1,11 @@ package touch.baton.fixture.domain; -import touch.baton.domain.feedback.SupporterFeedback; -import touch.baton.domain.feedback.vo.Description; -import touch.baton.domain.feedback.vo.ReviewType; -import touch.baton.domain.runner.Runner; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.supporter.Supporter; +import touch.baton.domain.feedback.command.SupporterFeedback; +import touch.baton.domain.feedback.command.vo.Description; +import touch.baton.domain.feedback.command.vo.ReviewType; +import touch.baton.domain.member.command.Runner; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.runnerpost.command.RunnerPost; import static touch.baton.fixture.vo.DescriptionFixture.description; diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java index e5b93fc55..903fd8196 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java @@ -1,11 +1,11 @@ package touch.baton.fixture.domain; -import touch.baton.domain.member.Member; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.vo.ReviewCount; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.vo.ReviewCountFixture; import java.util.ArrayList; diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterRunnerPostFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterRunnerPostFixture.java index c48140d79..7852fc6b4 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterRunnerPostFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterRunnerPostFixture.java @@ -1,9 +1,9 @@ package touch.baton.fixture.domain; -import touch.baton.domain.runnerpost.RunnerPost; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.supporter.SupporterRunnerPost; -import touch.baton.domain.supporter.vo.Message; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.member.command.vo.Message; +import touch.baton.domain.runnerpost.command.RunnerPost; public abstract class SupporterRunnerPostFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagFixture.java index b67a77f5a..56c222922 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagFixture.java @@ -1,8 +1,8 @@ package touch.baton.fixture.domain; -import touch.baton.domain.supporter.Supporter; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.TechnicalTag; public abstract class SupporterTechnicalTagFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagsFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagsFixture.java index da7600f6e..380362472 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagsFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterTechnicalTagsFixture.java @@ -1,7 +1,7 @@ package touch.baton.fixture.domain; -import touch.baton.domain.technicaltag.SupporterTechnicalTag; -import touch.baton.domain.technicaltag.SupporterTechnicalTags; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTag; +import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; import java.util.List; diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/TagFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/TagFixture.java index 2b659e111..9d27a5789 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/TagFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/TagFixture.java @@ -1,8 +1,8 @@ package touch.baton.fixture.domain; import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.tag.Tag; -import touch.baton.domain.tag.vo.TagReducedName; +import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.tag.command.vo.TagReducedName; import touch.baton.fixture.vo.TagNameFixture; public abstract class TagFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/TechnicalTagFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/TechnicalTagFixture.java index 3464d9c7d..b1ef78d5e 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/TechnicalTagFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/TechnicalTagFixture.java @@ -1,7 +1,7 @@ package touch.baton.fixture.domain; import touch.baton.domain.common.vo.TagName; -import touch.baton.domain.technicaltag.TechnicalTag; +import touch.baton.domain.technicaltag.command.TechnicalTag; import static touch.baton.fixture.vo.TagNameFixture.tagName; diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java index 10c443506..9a5c7e0a9 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.oauth.AuthorizationHeader; +import touch.baton.domain.oauth.command.AuthorizationHeader; public class AuthorizationHeaderFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/CompanyFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/CompanyFixture.java index 44532716e..3cd918eb9 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/CompanyFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/CompanyFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.member.vo.Company; +import touch.baton.domain.member.command.vo.Company; public abstract class CompanyFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/CuriousContentsFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/CuriousContentsFixture.java index 768f50574..de1f72670 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/CuriousContentsFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/CuriousContentsFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.runnerpost.vo.CuriousContents; +import touch.baton.domain.runnerpost.command.vo.CuriousContents; public abstract class CuriousContentsFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/DeadlineFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/DeadlineFixture.java index a706c4283..34e0f999c 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/DeadlineFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/DeadlineFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.runnerpost.vo.Deadline; +import touch.baton.domain.runnerpost.command.vo.Deadline; import java.time.LocalDateTime; diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/DescriptionFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/DescriptionFixture.java index 9354b8828..f7762441a 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/DescriptionFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/DescriptionFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.feedback.vo.Description; +import touch.baton.domain.feedback.command.vo.Description; public abstract class DescriptionFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java index 1d923d90f..98de3a810 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.oauth.token.ExpireDate; +import touch.baton.domain.oauth.command.token.ExpireDate; import java.time.LocalDateTime; diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/GithubUrlFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/GithubUrlFixture.java index 6eedbdc2d..4f3158ca3 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/GithubUrlFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/GithubUrlFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.member.vo.GithubUrl; +import touch.baton.domain.member.command.vo.GithubUrl; public abstract class GithubUrlFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/ImageUrlFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/ImageUrlFixture.java index 3f3667ffe..e07b7c8f8 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/ImageUrlFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/ImageUrlFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.member.vo.ImageUrl; +import touch.baton.domain.member.command.vo.ImageUrl; public abstract class ImageUrlFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/ImplementedContentsFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/ImplementedContentsFixture.java index 468933ee3..fea905937 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/ImplementedContentsFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/ImplementedContentsFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.runnerpost.vo.ImplementedContents; +import touch.baton.domain.runnerpost.command.vo.ImplementedContents; public abstract class ImplementedContentsFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/IntroductionFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/IntroductionFixture.java index 596bca95d..446e888fd 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/IntroductionFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/IntroductionFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.common.vo.Introduction; +import touch.baton.domain.member.command.vo.Introduction; public abstract class IntroductionFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/MemberNameFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/MemberNameFixture.java index 2160545eb..a86d608af 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/MemberNameFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/MemberNameFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.member.vo.MemberName; +import touch.baton.domain.member.command.vo.MemberName; public abstract class MemberNameFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/MessageFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/MessageFixture.java index 7cd2c8c5a..041bf090f 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/MessageFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/MessageFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.supporter.vo.Message; +import touch.baton.domain.member.command.vo.Message; public abstract class MessageFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationMessageFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationMessageFixture.java new file mode 100644 index 000000000..688ada0a2 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationMessageFixture.java @@ -0,0 +1,13 @@ +package touch.baton.fixture.vo; + +import touch.baton.domain.notification.command.vo.NotificationMessage; + +public abstract class NotificationMessageFixture { + + private NotificationMessageFixture() { + } + + public static NotificationMessage notificationMessage(final String value) { + return new NotificationMessage(value); + } +} diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationReferencedIdFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationReferencedIdFixture.java new file mode 100644 index 000000000..56b0d803d --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationReferencedIdFixture.java @@ -0,0 +1,13 @@ +package touch.baton.fixture.vo; + +import touch.baton.domain.notification.command.vo.NotificationReferencedId; + +public abstract class NotificationReferencedIdFixture { + + private NotificationReferencedIdFixture() { + } + + public static NotificationReferencedId notificationReferencedId(final Long value) { + return new NotificationReferencedId(value); + } +} diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationTitleFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationTitleFixture.java new file mode 100644 index 000000000..a7d04ec8c --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/NotificationTitleFixture.java @@ -0,0 +1,13 @@ +package touch.baton.fixture.vo; + +import touch.baton.domain.notification.command.vo.NotificationTitle; + +public abstract class NotificationTitleFixture { + + private NotificationTitleFixture() { + } + + public static NotificationTitle notificationTitle(final String value) { + return new NotificationTitle(value); + } +} diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/OauthIdFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/OauthIdFixture.java index 519ba6766..331beb1da 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/OauthIdFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/OauthIdFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.member.vo.OauthId; +import touch.baton.domain.member.command.vo.OauthId; public abstract class OauthIdFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/PostscriptContentsFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/PostscriptContentsFixture.java index c03fe92fd..162566d52 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/PostscriptContentsFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/PostscriptContentsFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.runnerpost.vo.PostscriptContents; +import touch.baton.domain.runnerpost.command.vo.PostscriptContents; public abstract class PostscriptContentsFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/PullRequestUrlFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/PullRequestUrlFixture.java index a28447b46..f49d15436 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/PullRequestUrlFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/PullRequestUrlFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.runnerpost.vo.PullRequestUrl; +import touch.baton.domain.runnerpost.command.vo.PullRequestUrl; public abstract class PullRequestUrlFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/ReviewCountFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/ReviewCountFixture.java index 59a70efa8..decac6fad 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/ReviewCountFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/ReviewCountFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.supporter.vo.ReviewCount; +import touch.baton.domain.member.command.vo.ReviewCount; public abstract class ReviewCountFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/SocialIdFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/SocialIdFixture.java index 2552b5314..cadfdea60 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/SocialIdFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/SocialIdFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.member.vo.SocialId; +import touch.baton.domain.member.command.vo.SocialId; public abstract class SocialIdFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/TitleFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/TitleFixture.java index 1feb686cf..8b5fc8864 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/TitleFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/TitleFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.common.vo.Title; +import touch.baton.domain.runnerpost.command.vo.Title; public abstract class TitleFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/TokenFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/TokenFixture.java index 1ca72494d..bd53649e4 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/TokenFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/TokenFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.oauth.token.Token; +import touch.baton.domain.oauth.command.token.Token; public abstract class TokenFixture { diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/WatchedCountFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/WatchedCountFixture.java index 54b5551cb..cd482a1a6 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/WatchedCountFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/WatchedCountFixture.java @@ -1,6 +1,6 @@ package touch.baton.fixture.vo; -import touch.baton.domain.common.vo.WatchedCount; +import touch.baton.domain.runnerpost.command.vo.WatchedCount; public abstract class WatchedCountFixture { diff --git a/backend/baton/src/test/java/touch/baton/infra/auth/jwt/JwtEncoderAndDecoderTest.java b/backend/baton/src/test/java/touch/baton/infra/auth/jwt/JwtEncoderAndDecoderTest.java index 664913a32..13170e70a 100644 --- a/backend/baton/src/test/java/touch/baton/infra/auth/jwt/JwtEncoderAndDecoderTest.java +++ b/backend/baton/src/test/java/touch/baton/infra/auth/jwt/JwtEncoderAndDecoderTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import touch.baton.domain.oauth.exception.OauthRequestException; +import touch.baton.domain.oauth.command.exception.OauthRequestException; import java.util.Map; From 9b0371aef8d0e7c74f74949cf7c52fc3dc6e8a89 Mon Sep 17 00:00:00 2001 From: "HyunSeo Park (Hyena)" Date: Wed, 11 Oct 2023 18:53:16 +0900 Subject: [PATCH 3/8] =?UTF-8?q?deploy=20=EC=84=9C=EB=B8=8C=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=88=98=EC=A0=95=20(#663)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 서브모듈 --- backend/baton/secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/baton/secret b/backend/baton/secret index ca9b453e7..30c881514 160000 --- a/backend/baton/secret +++ b/backend/baton/secret @@ -1 +1 @@ -Subproject commit ca9b453e7e8e730dc6fb2ecf4431d6aa48cc2a5e +Subproject commit 30c881514a5a188944b2eb418838209f56590ba0 From c622a2ce23ec91de1e1ca81ad5c723350fc77a7d Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 17 Feb 2024 19:43:51 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EA=B4=80=EB=A6=AC=EB=A5=BC=20Redis?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#710)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: redis 설정 추가 * refactor: RefreshToken 관리를 redis로 업데이트 * test: refreshToken 관련 service 테스트 수정 * refactor: EmbbedRedis 설정 제거 * feat: Redis Test Container 설정 추가 * refactor: 테스트가 용이하게 RefreshToken TTL을 외부에서 관리 * test: OauthComand관련 테스트 수정 * test: Restdocs 테스트 수정 * refactor: OauthCommandService 생성자에 refreshTokenExpireMinutes를 받도록 수정 * test: RefreshToken 추가 * test: 사용하지 않는 RefreshToken 삭제 * refactor: RefreshToken2 를 RefreshToken으로 이름 변경 * refactor: expire minutes 이름 변경 * test: RefreshTokenFixture로 리팩토링 * refactor: Facade 패턴 적용 * refactor: ttl 단위를 분으로 변경 * refactor: RedisConfig에 username과 password 추가 * feat: docker-compose 에 redis 설정 추가 * refactor: 사용하지 않는 Restemplate 제거 * refactor: 스프링 부트 버전 3.1.8로 업그레이드 * refactor: redis.conf bind를 localhost만 가능하도록 변경 --- backend/baton/build.gradle | 4 +- backend/baton/docker-compose.yaml | 10 + backend/baton/redis/redis.conf | 2277 +++++++++++++++++ backend/baton/redis/users.acl | 1 + backend/baton/secret | 2 +- .../java/touch/baton/config/RedisConfig.java | 37 + .../controller/OauthCommandController.java | 14 +- .../RefreshTokenCommandRepository.java | 14 +- .../command/service/OauthCommandService.java | 81 +- .../oauth/command/service/TokenFacade.java | 75 + .../oauth/command/token/ExpireDate.java | 40 - .../oauth/command/token/RefreshToken.java | 77 +- .../domain/oauth/command/token/Token.java | 9 +- .../baton/src/main/resources/application.yml | 11 +- .../assure/oauth/OauthAssuredSupport.java | 7 +- .../oauth/OauthRefreshTokenAssuredTest.java | 13 +- .../TestRefreshTokenRepository.java | 35 +- .../touch/baton/config/AssuredTestConfig.java | 3 + .../auth/oauth/MockRefreshTokenConfig.java | 35 - .../infra/database/TestRedisConfig.java | 37 + .../database/TestRedisContainerConfig.java | 24 + .../oauth/github/GithubOauthApiTest.java | 8 +- .../oauth/token/RefreshTokenApiTest.java | 9 +- .../RefreshTokenCommandRepositoryTest.java | 110 - .../OauthCommandServiceDeleteTest.java | 16 +- .../OauthCommandServiceUpdateTest.java | 99 +- .../service/TokenFacadeDeleteTest.java | 48 + .../service/TokenFacadeUpdateTest.java | 100 + .../oauth/command/token/RefreshTokenTest.java | 105 +- .../fixture/domain/RefreshTokenFixture.java | 24 +- .../baton/fixture/domain/TokensFixture.java | 15 + .../baton/fixture/vo/AccessTokenFixture.java | 17 + .../vo/AuthorizationHeaderFixture.java | 2 +- .../baton/fixture/vo/ExpireDateFixture.java | 15 - .../baton/src/test/resources/application.yml | 10 +- 35 files changed, 2817 insertions(+), 567 deletions(-) create mode 100644 backend/baton/redis/redis.conf create mode 100644 backend/baton/redis/users.acl create mode 100644 backend/baton/src/main/java/touch/baton/config/RedisConfig.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/command/service/TokenFacade.java delete mode 100644 backend/baton/src/main/java/touch/baton/domain/oauth/command/token/ExpireDate.java delete mode 100644 backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java create mode 100644 backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisConfig.java create mode 100644 backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisContainerConfig.java delete mode 100644 backend/baton/src/test/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeDeleteTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeUpdateTest.java create mode 100644 backend/baton/src/test/java/touch/baton/fixture/domain/TokensFixture.java create mode 100644 backend/baton/src/test/java/touch/baton/fixture/vo/AccessTokenFixture.java delete mode 100644 backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java diff --git a/backend/baton/build.gradle b/backend/baton/build.gradle index 559beafe2..32e8c9d37 100644 --- a/backend/baton/build.gradle +++ b/backend/baton/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'java-test-fixtures' - id 'org.springframework.boot' version '3.1.1' + id 'org.springframework.boot' version '3.1.8' id 'io.spring.dependency-management' version '1.1.0' id 'org.asciidoctor.jvm.convert' version '3.3.2' } @@ -66,6 +66,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' // database + implementation 'org.springframework.boot:spring-boot-starter-data-redis' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' @@ -79,6 +80,7 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured' testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:testcontainers:1.17.2' testImplementation 'org.springframework.boot:spring-boot-testcontainers' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' testImplementation 'org.testcontainers:mysql' diff --git a/backend/baton/docker-compose.yaml b/backend/baton/docker-compose.yaml index 3f0d6fd01..33aa6d750 100644 --- a/backend/baton/docker-compose.yaml +++ b/backend/baton/docker-compose.yaml @@ -1,6 +1,7 @@ services: mysql: image: 'mysql:latest' + container_name: 'baton-mysql' environment: - 'MYSQL_DATABASE=mydatabase' - 'MYSQL_PASSWORD=secret' @@ -8,3 +9,12 @@ services: - 'MYSQL_USER=myuser' ports: - '3307:3306' + redis: + image: 'redis:7.0-alpine' + container_name: 'baton-redis' + ports: + - '16379:6379' + command: redis-server /usr/local/etc/redis/redis.conf + volumes: + - ./redis/redis.conf:/usr/local/etc/redis/redis.conf + - ./redis/users.acl:/etc/redis/users.acl diff --git a/backend/baton/redis/redis.conf b/backend/baton/redis/redis.conf new file mode 100644 index 000000000..0cb3ddcc2 --- /dev/null +++ b/backend/baton/redis/redis.conf @@ -0,0 +1,2277 @@ +# Redis configuration file example. +# +# Note that in order to read the configuration file, Redis must be +# started with the file path as first argument: +# +# ./redis-server /path/to/redis.conf + +# Note on units: when memory size is needed, it is possible to specify +# it in the usual form of 1k 5GB 4M and so forth: +# +# 1k => 1000 bytes +# 1kb => 1024 bytes +# 1m => 1000000 bytes +# 1mb => 1024*1024 bytes +# 1g => 1000000000 bytes +# 1gb => 1024*1024*1024 bytes +# +# units are case insensitive so 1GB 1Gb 1gB are all the same. + +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis servers but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Note that option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# Included paths may contain wildcards. All files matching the wildcards will +# be included in alphabetical order. +# Note that if an include path contains a wildcards but no files match it when +# the server is started, the include statement will be ignored and no error will +# be emitted. It is safe, therefore, to include wildcard files from empty +# directories. +# +# include /path/to/local.conf +# include /path/to/other.conf +# include /path/to/fragments/*.conf +# + +################################## MODULES ##################################### + +# Load modules at startup. If the server is not able to load modules +# it will abort. It is possible to use multiple loadmodule directives. +# +# loadmodule /path/to/my_module.so +# loadmodule /path/to/other_module.so + +################################## NETWORK ##################################### + +# By default, if no "bind" configuration directive is specified, Redis listens +# for connections from all available network interfaces on the host machine. +# It is possible to listen to just one or multiple selected interfaces using +# the "bind" configuration directive, followed by one or more IP addresses. +# Each address can be prefixed by "-", which means that redis will not fail to +# start if the address is not available. Being not available only refers to +# addresses that does not correspond to any network interface. Addresses that +# are already in use will always fail, and unsupported protocols will always BE +# silently skipped. +# +# Examples: +# +# bind 192.168.1.100 10.0.0.1 # listens on two specific IPv4 addresses +# bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6 +# bind * -::* # like the default, all available interfaces +# +# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the +# internet, binding to all the interfaces is dangerous and will expose the +# instance to everybody on the internet. So by default we uncomment the +# following bind directive, that will force Redis to listen only on the +# IPv4 and IPv6 (if available) loopback interface addresses (this means Redis +# will only be able to accept client connections from the same host that it is +# running on). +# +# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES +# COMMENT OUT THE FOLLOWING LINE. +# +# You will also need to set a password unless you explicitly disable protected +# mode. +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bind 127.0.0.1 -::1 + +# By default, outgoing connections (from replica to master, from Sentinel to +# instances, cluster bus, etc.) are not bound to a specific local address. In +# most cases, this means the operating system will handle that based on routing +# and the interface through which the connection goes out. +# +# Using bind-source-addr it is possible to configure a specific address to bind +# to, which may also affect how the connection gets routed. +# +# Example: +# +# bind-source-addr 10.0.0.1 + +# Protected mode is a layer of security protection, in order to avoid that +# Redis instances left open on the internet are accessed and exploited. +# +# When protected mode is on and the default user has no password, the server +# only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address +# (::1) or Unix domain sockets. +# +# By default protected mode is enabled. You should disable it only if +# you are sure you want clients from other hosts to connect to Redis +# even if no authentication is configured. +protected-mode no + +# Redis uses default hardened security configuration directives to reduce the +# attack surface on innocent users. Therefore, several sensitive configuration +# directives are immutable, and some potentially-dangerous commands are blocked. +# +# Configuration directives that control files that Redis writes to (e.g., 'dir' +# and 'dbfilename') and that aren't usually modified during runtime +# are protected by making them immutable. +# +# Commands that can increase the attack surface of Redis and that aren't usually +# called by users are blocked by default. +# +# These can be exposed to either all connections or just local ones by setting +# each of the configs listed below to either of these values: +# +# no - Block for any connection (remain immutable) +# yes - Allow for any connection (no protection) +# local - Allow only for local connections. Ones originating from the +# IPv4 address (127.0.0.1), IPv6 address (::1) or Unix domain sockets. +# +# enable-protected-configs no +# enable-debug-command no +# enable-module-command no + +# Accept connections on the specified port, default is 6379 (IANA #815344). +# If port 0 is specified Redis will not listen on a TCP socket. +port 6379 + +# TCP listen() backlog. +# +# In high requests-per-second environments you need a high backlog in order +# to avoid slow clients connection issues. Note that the Linux kernel +# will silently truncate it to the value of /proc/sys/net/core/somaxconn so +# make sure to raise both the value of somaxconn and tcp_max_syn_backlog +# in order to get the desired effect. +tcp-backlog 511 + +# Unix socket. +# +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +# unixsocket /run/redis.sock +# unixsocketperm 700 + +# Close the connection after a client is idle for N seconds (0 to disable) +timeout 0 + +# TCP keepalive. +# +# If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence +# of communication. This is useful for two reasons: +# +# 1) Detect dead peers. +# 2) Force network equipment in the middle to consider the connection to be +# alive. +# +# On Linux, the specified value (in seconds) is the period used to send ACKs. +# Note that to close the connection the double of the time is needed. +# On other kernels the period depends on the kernel configuration. +# +# A reasonable value for this option is 300 seconds, which is the new +# Redis default starting with Redis 3.2.1. +tcp-keepalive 300 + +# Apply OS-specific mechanism to mark the listening socket with the specified +# ID, to support advanced routing and filtering capabilities. +# +# On Linux, the ID represents a connection mark. +# On FreeBSD, the ID represents a socket cookie ID. +# On OpenBSD, the ID represents a route table ID. +# +# The default value is 0, which implies no marking is required. +# socket-mark-id 0 + +################################# TLS/SSL ##################################### + +# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration +# directive can be used to define TLS-listening ports. To enable TLS on the +# default port, use: +# +# port 0 +# tls-port 6379 + +# Configure a X.509 certificate and private key to use for authenticating the +# server to connected clients, masters or cluster peers. These files should be +# PEM formatted. +# +# tls-cert-file redis.crt +# tls-key-file redis.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-key-file-pass secret + +# Normally Redis uses the same certificate for both server functions (accepting +# connections) and client functions (replicating from a master, establishing +# cluster bus connections, etc.). +# +# Sometimes certificates are issued with attributes that designate them as +# client-only or server-only certificates. In that case it may be desired to use +# different certificates for incoming (server) and outgoing (client) +# connections. To do that, use the following directives: +# +# tls-client-cert-file client.crt +# tls-client-key-file client.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-client-key-file-pass secret + +# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange, +# required by older versions of OpenSSL (<3.0). Newer versions do not require +# this configuration and recommend against it. +# +# tls-dh-params-file redis.dh + +# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL +# clients and peers. Redis requires an explicit configuration of at least one +# of these, and will not implicitly use the system wide configuration. +# +# tls-ca-cert-file ca.crt +# tls-ca-cert-dir /etc/ssl/certs + +# By default, clients (including replica servers) on a TLS port are required +# to authenticate using valid client side certificates. +# +# If "no" is specified, client certificates are not required and not accepted. +# If "optional" is specified, client certificates are accepted and must be +# valid if provided, but are not required. +# +# tls-auth-clients no +# tls-auth-clients optional + +# By default, a Redis replica does not attempt to establish a TLS connection +# with its master. +# +# Use the following directive to enable TLS on replication links. +# +# tls-replication yes + +# By default, the Redis Cluster bus uses a plain TCP connection. To enable +# TLS for the bus protocol, use the following directive: +# +# tls-cluster yes + +# By default, only TLSv1.2 and TLSv1.3 are enabled and it is highly recommended +# that older formally deprecated versions are kept disabled to reduce the attack surface. +# You can explicitly specify TLS versions to support. +# Allowed values are case insensitive and include "TLSv1", "TLSv1.1", "TLSv1.2", +# "TLSv1.3" (OpenSSL >= 1.1.1) or any combination. +# To enable only TLSv1.2 and TLSv1.3, use: +# +# tls-protocols "TLSv1.2 TLSv1.3" + +# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information +# about the syntax of this string. +# +# Note: this configuration applies only to <= TLSv1.2. +# +# tls-ciphers DEFAULT:!MEDIUM + +# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more +# information about the syntax of this string, and specifically for TLSv1.3 +# ciphersuites. +# +# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256 + +# When choosing a cipher, use the server's preference instead of the client +# preference. By default, the server follows the client's preference. +# +# tls-prefer-server-ciphers yes + +# By default, TLS session caching is enabled to allow faster and less expensive +# reconnections by clients that support it. Use the following directive to disable +# caching. +# +# tls-session-caching no + +# Change the default number of TLS sessions cached. A zero value sets the cache +# to unlimited size. The default size is 20480. +# +# tls-session-cache-size 5000 + +# Change the default timeout of cached TLS sessions. The default timeout is 300 +# seconds. +# +# tls-session-cache-timeout 60 + +################################# GENERAL ##################################### + +# By default Redis does not run as a daemon. Use 'yes' if you need it. +# Note that Redis will write a pid file in /var/run/redis.pid when daemonized. +# When Redis is supervised by upstart or systemd, this parameter has no impact. +daemonize no + +# If you run Redis from upstart or systemd, Redis can interact with your +# supervision tree. Options: +# supervised no - no supervision interaction +# supervised upstart - signal upstart by putting Redis into SIGSTOP mode +# requires "expect stop" in your upstart job config +# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET +# on startup, and updating Redis status on a regular +# basis. +# supervised auto - detect upstart or systemd method based on +# UPSTART_JOB or NOTIFY_SOCKET environment variables +# Note: these supervision methods only signal "process is ready." +# They do not enable continuous pings back to your supervisor. +# +# The default is "no". To run under upstart/systemd, you can simply uncomment +# the line below: +# +# supervised auto + +# If a pid file is specified, Redis writes it where specified at startup +# and removes it at exit. +# +# When the server runs non daemonized, no pid file is created if none is +# specified in the configuration. When the server is daemonized, the pid file +# is used even if not specified, defaulting to "/var/run/redis.pid". +# +# Creating a pid file is best effort: if Redis is not able to create it +# nothing bad happens, the server will start and run normally. +# +# Note that on modern Linux systems "/run/redis.pid" is more conforming +# and should be used instead. +pidfile /var/run/redis_6379.pid + +# Specify the server verbosity level. +# This can be one of: +# debug (a lot of information, useful for development/testing) +# verbose (many rarely useful info, but not a mess like the debug level) +# notice (moderately verbose, what you want in production probably) +# warning (only very important / critical messages are logged) +loglevel notice + +# Specify the log file name. Also the empty string can be used to force +# Redis to log on the standard output. Note that if you use standard +# output for logging but daemonize, logs will be sent to /dev/null +logfile "" + +# To enable logging to the system logger, just set 'syslog-enabled' to yes, +# and optionally update the other syslog parameters to suit your needs. +# syslog-enabled no + +# Specify the syslog identity. +# syslog-ident redis + +# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. +# syslog-facility local0 + +# To disable the built in crash log, which will possibly produce cleaner core +# dumps when they are needed, uncomment the following: +# +# crash-log-enabled no + +# To disable the fast memory check that's run as part of the crash log, which +# will possibly let redis terminate sooner, uncomment the following: +# +# crash-memcheck-enabled no + +# Set the number of databases. The default database is DB 0, you can select +# a different one on a per-connection basis using SELECT where +# dbid is a number between 0 and 'databases'-1 +databases 16 + +# By default Redis shows an ASCII art logo only when started to log to the +# standard output and if the standard output is a TTY and syslog logging is +# disabled. Basically this means that normally a logo is displayed only in +# interactive sessions. +# +# However it is possible to force the pre-4.0 behavior and always show a +# ASCII art logo in startup logs by setting the following option to yes. +always-show-logo no + +# By default, Redis modifies the process title (as seen in 'top' and 'ps') to +# provide some runtime information. It is possible to disable this and leave +# the process name as executed by setting the following to no. +set-proc-title yes + +# When changing the process title, Redis uses the following template to construct +# the modified title. +# +# Template variables are specified in curly brackets. The following variables are +# supported: +# +# {title} Name of process as executed if parent, or type of child process. +# {listen-addr} Bind address or '*' followed by TCP or TLS port listening on, or +# Unix socket if only that's available. +# {server-mode} Special mode, i.e. "[sentinel]" or "[cluster]". +# {port} TCP port listening on, or 0. +# {tls-port} TLS port listening on, or 0. +# {unixsocket} Unix domain socket listening on, or "". +# {config-file} Name of configuration file used. +# +proc-title-template "{title} {listen-addr} {server-mode}" + +################################ SNAPSHOTTING ################################ + +# Save the DB to disk. +# +# save [ ...] +# +# Redis will save the DB if the given number of seconds elapsed and it +# surpassed the given number of write operations against the DB. +# +# Snapshotting can be completely disabled with a single empty string argument +# as in following example: +# +# save "" +# +# Unless specified otherwise, by default Redis will save the DB: +# * After 3600 seconds (an hour) if at least 1 change was performed +# * After 300 seconds (5 minutes) if at least 100 changes were performed +# * After 60 seconds if at least 10000 changes were performed +# +# You can set these explicitly by uncommenting the following line. +# +# save 3600 1 300 100 60 10000 + +# By default Redis will stop accepting writes if RDB snapshots are enabled +# (at least one save point) and the latest background save failed. +# This will make the user aware (in a hard way) that data is not persisting +# on disk properly, otherwise chances are that no one will notice and some +# disaster will happen. +# +# If the background saving process will start working again Redis will +# automatically allow writes again. +# +# However if you have setup your proper monitoring of the Redis server +# and persistence, you may want to disable this feature so that Redis will +# continue to work as usual even if there are problems with disk, +# permissions, and so forth. +stop-writes-on-bgsave-error yes + +# Compress string objects using LZF when dump .rdb databases? +# By default compression is enabled as it's almost always a win. +# If you want to save some CPU in the saving child set it to 'no' but +# the dataset will likely be bigger if you have compressible values or keys. +rdbcompression yes + +# Since version 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + +# Enables or disables full sanitization checks for ziplist and listpack etc when +# loading an RDB or RESTORE payload. This reduces the chances of a assertion or +# crash later on while processing commands. +# Options: +# no - Never perform full sanitization +# yes - Always perform full sanitization +# clients - Perform full sanitization only for user connections. +# Excludes: RDB files, RESTORE commands received from the master +# connection, and client connections which have the +# skip-sanitize-payload ACL flag. +# The default should be 'clients' but since it currently affects cluster +# resharding via MIGRATE, it is temporarily set to 'no' by default. +# +# sanitize-dump-payload no + +# The filename where to dump the DB +dbfilename dump.rdb + +# Remove RDB files used by replication in instances without persistence +# enabled. By default this option is disabled, however there are environments +# where for regulations or other security concerns, RDB files persisted on +# disk by masters in order to feed replicas, or stored on disk by replicas +# in order to load them for the initial synchronization, should be deleted +# ASAP. Note that this option ONLY WORKS in instances that have both AOF +# and RDB persistence disabled, otherwise is completely ignored. +# +# An alternative (and sometimes better) way to obtain the same effect is +# to use diskless replication on both master and replicas instances. However +# in the case of replicas, diskless is not always an option. +rdb-del-sync-files no + +# The working directory. +# +# The DB will be written inside this directory, with the filename specified +# above using the 'dbfilename' configuration directive. +# +# The Append Only File will also be created inside this directory. +# +# Note that you must specify a directory here, not a file name. +dir ./ + +################################# REPLICATION ################################# + +# Master-Replica replication. Use replicaof to make a Redis instance a copy of +# another Redis server. A few things to understand ASAP about Redis replication. +# +# +------------------+ +---------------+ +# | Master | ---> | Replica | +# | (receive writes) | | (exact copy) | +# +------------------+ +---------------+ +# +# 1) Redis replication is asynchronous, but you can configure a master to +# stop accepting writes if it appears to be not connected with at least +# a given number of replicas. +# 2) Redis replicas are able to perform a partial resynchronization with the +# master if the replication link is lost for a relatively small amount of +# time. You may want to configure the replication backlog size (see the next +# sections of this file) with a sensible value depending on your needs. +# 3) Replication is automatic and does not need user intervention. After a +# network partition replicas automatically try to reconnect to masters +# and resynchronize with them. +# +# replicaof + +# If the master is password protected (using the "requirepass" configuration +# directive below) it is possible to tell the replica to authenticate before +# starting the replication synchronization process, otherwise the master will +# refuse the replica request. +# +# masterauth +# +# However this is not enough if you are using Redis ACLs (for Redis version +# 6 or greater), and the default user is not capable of running the PSYNC +# command and/or other commands needed for replication. In this case it's +# better to configure a special user to use with replication, and specify the +# masteruser configuration as such: +# +# masteruser +# +# When masteruser is specified, the replica will authenticate against its +# master using the new AUTH form: AUTH . + +# When a replica loses its connection with the master, or when the replication +# is still in progress, the replica can act in two different ways: +# +# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will +# still reply to client requests, possibly with out of date data, or the +# data set may just be empty if this is the first synchronization. +# +# 2) If replica-serve-stale-data is set to 'no' the replica will reply with error +# "MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'" +# to all data access commands, excluding commands such as: +# INFO, REPLICAOF, AUTH, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, +# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, +# HOST and LATENCY. +# +replica-serve-stale-data yes + +# You can configure a replica instance to accept writes or not. Writing against +# a replica instance may be useful to store some ephemeral data (because data +# written on a replica will be easily deleted after resync with the master) but +# may also cause problems if clients are writing to it because of a +# misconfiguration. +# +# Since Redis 2.6 by default replicas are read-only. +# +# Note: read only replicas are not designed to be exposed to untrusted clients +# on the internet. It's just a protection layer against misuse of the instance. +# Still a read only replica exports by default all the administrative commands +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve +# security of read only replicas using 'rename-command' to shadow all the +# administrative / dangerous commands. +replica-read-only yes + +# Replication SYNC strategy: disk or socket. +# +# New replicas and reconnecting replicas that are not able to continue the +# replication process just receiving differences, need to do what is called a +# "full synchronization". An RDB file is transmitted from the master to the +# replicas. +# +# The transmission can happen in two different ways: +# +# 1) Disk-backed: The Redis master creates a new process that writes the RDB +# file on disk. Later the file is transferred by the parent +# process to the replicas incrementally. +# 2) Diskless: The Redis master creates a new process that directly writes the +# RDB file to replica sockets, without touching the disk at all. +# +# With disk-backed replication, while the RDB file is generated, more replicas +# can be queued and served with the RDB file as soon as the current child +# producing the RDB file finishes its work. With diskless replication instead +# once the transfer starts, new replicas arriving will be queued and a new +# transfer will start when the current one terminates. +# +# When diskless replication is used, the master waits a configurable amount of +# time (in seconds) before starting the transfer in the hope that multiple +# replicas will arrive and the transfer can be parallelized. +# +# With slow disks and fast (large bandwidth) networks, diskless replication +# works better. +repl-diskless-sync yes + +# When diskless replication is enabled, it is possible to configure the delay +# the server waits in order to spawn the child that transfers the RDB via socket +# to the replicas. +# +# This is important since once the transfer starts, it is not possible to serve +# new replicas arriving, that will be queued for the next RDB transfer, so the +# server waits a delay in order to let more replicas arrive. +# +# The delay is specified in seconds, and by default is 5 seconds. To disable +# it entirely just set it to 0 seconds and the transfer will start ASAP. +repl-diskless-sync-delay 5 + +# When diskless replication is enabled with a delay, it is possible to let +# the replication start before the maximum delay is reached if the maximum +# number of replicas expected have connected. Default of 0 means that the +# maximum is not defined and Redis will wait the full delay. +repl-diskless-sync-max-replicas 0 + +# ----------------------------------------------------------------------------- +# WARNING: RDB diskless load is experimental. Since in this setup the replica +# does not immediately store an RDB on disk, it may cause data loss during +# failovers. RDB diskless load + Redis modules not handling I/O reads may also +# cause Redis to abort in case of I/O errors during the initial synchronization +# stage with the master. Use only if you know what you are doing. +# ----------------------------------------------------------------------------- +# +# Replica can load the RDB it reads from the replication link directly from the +# socket, or store the RDB to a file and read that file after it was completely +# received from the master. +# +# In many cases the disk is slower than the network, and storing and loading +# the RDB file may increase replication time (and even increase the master's +# Copy on Write memory and replica buffers). +# However, parsing the RDB file directly from the socket may mean that we have +# to flush the contents of the current database before the full rdb was +# received. For this reason we have the following options: +# +# "disabled" - Don't use diskless load (store the rdb file to the disk first) +# "on-empty-db" - Use diskless load only when it is completely safe. +# "swapdb" - Keep current db contents in RAM while parsing the data directly +# from the socket. Replicas in this mode can keep serving current +# data set while replication is in progress, except for cases where +# they can't recognize master as having a data set from same +# replication history. +# Note that this requires sufficient memory, if you don't have it, +# you risk an OOM kill. +repl-diskless-load disabled + +# Master send PINGs to its replicas in a predefined interval. It's possible to +# change this interval with the repl_ping_replica_period option. The default +# value is 10 seconds. +# +# repl-ping-replica-period 10 + +# The following option sets the replication timeout for: +# +# 1) Bulk transfer I/O during SYNC, from the point of view of replica. +# 2) Master timeout from the point of view of replicas (data, pings). +# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings). +# +# It is important to make sure that this value is greater than the value +# specified for repl-ping-replica-period otherwise a timeout will be detected +# every time there is low traffic between the master and the replica. The default +# value is 60 seconds. +# +# repl-timeout 60 + +# Disable TCP_NODELAY on the replica socket after SYNC? +# +# If you select "yes" Redis will use a smaller number of TCP packets and +# less bandwidth to send data to replicas. But this can add a delay for +# the data to appear on the replica side, up to 40 milliseconds with +# Linux kernels using a default configuration. +# +# If you select "no" the delay for data to appear on the replica side will +# be reduced but more bandwidth will be used for replication. +# +# By default we optimize for low latency, but in very high traffic conditions +# or when the master and replicas are many hops away, turning this to "yes" may +# be a good idea. +repl-disable-tcp-nodelay no + +# Set the replication backlog size. The backlog is a buffer that accumulates +# replica data when replicas are disconnected for some time, so that when a +# replica wants to reconnect again, often a full resync is not needed, but a +# partial resync is enough, just passing the portion of data the replica +# missed while disconnected. +# +# The bigger the replication backlog, the longer the replica can endure the +# disconnect and later be able to perform a partial resynchronization. +# +# The backlog is only allocated if there is at least one replica connected. +# +# repl-backlog-size 1mb + +# After a master has no connected replicas for some time, the backlog will be +# freed. The following option configures the amount of seconds that need to +# elapse, starting from the time the last replica disconnected, for the backlog +# buffer to be freed. +# +# Note that replicas never free the backlog for timeout, since they may be +# promoted to masters later, and should be able to correctly "partially +# resynchronize" with other replicas: hence they should always accumulate backlog. +# +# A value of 0 means to never release the backlog. +# +# repl-backlog-ttl 3600 + +# The replica priority is an integer number published by Redis in the INFO +# output. It is used by Redis Sentinel in order to select a replica to promote +# into a master if the master is no longer working correctly. +# +# A replica with a low priority number is considered better for promotion, so +# for instance if there are three replicas with priority 10, 100, 25 Sentinel +# will pick the one with priority 10, that is the lowest. +# +# However a special priority of 0 marks the replica as not able to perform the +# role of master, so a replica with priority of 0 will never be selected by +# Redis Sentinel for promotion. +# +# By default the priority is 100. +replica-priority 100 + +# The propagation error behavior controls how Redis will behave when it is +# unable to handle a command being processed in the replication stream from a master +# or processed while reading from an AOF file. Errors that occur during propagation +# are unexpected, and can cause data inconsistency. However, there are edge cases +# in earlier versions of Redis where it was possible for the server to replicate or persist +# commands that would fail on future versions. For this reason the default behavior +# is to ignore such errors and continue processing commands. +# +# If an application wants to ensure there is no data divergence, this configuration +# should be set to 'panic' instead. The value can also be set to 'panic-on-replicas' +# to only panic when a replica encounters an error on the replication stream. One of +# these two panic values will become the default value in the future once there are +# sufficient safety mechanisms in place to prevent false positive crashes. +# +# propagation-error-behavior ignore + +# Replica ignore disk write errors controls the behavior of a replica when it is +# unable to persist a write command received from its master to disk. By default, +# this configuration is set to 'no' and will crash the replica in this condition. +# It is not recommended to change this default, however in order to be compatible +# with older versions of Redis this config can be toggled to 'yes' which will just +# log a warning and execute the write command it got from the master. +# +# replica-ignore-disk-write-errors no + +# ----------------------------------------------------------------------------- +# By default, Redis Sentinel includes all replicas in its reports. A replica +# can be excluded from Redis Sentinel's announcements. An unannounced replica +# will be ignored by the 'sentinel replicas ' command and won't be +# exposed to Redis Sentinel's clients. +# +# This option does not change the behavior of replica-priority. Even with +# replica-announced set to 'no', the replica can be promoted to master. To +# prevent this behavior, set replica-priority to 0. +# +# replica-announced yes + +# It is possible for a master to stop accepting writes if there are less than +# N replicas connected, having a lag less or equal than M seconds. +# +# The N replicas need to be in "online" state. +# +# The lag in seconds, that must be <= the specified value, is calculated from +# the last ping received from the replica, that is usually sent every second. +# +# This option does not GUARANTEE that N replicas will accept the write, but +# will limit the window of exposure for lost writes in case not enough replicas +# are available, to the specified number of seconds. +# +# For example to require at least 3 replicas with a lag <= 10 seconds use: +# +# min-replicas-to-write 3 +# min-replicas-max-lag 10 +# +# Setting one or the other to 0 disables the feature. +# +# By default min-replicas-to-write is set to 0 (feature disabled) and +# min-replicas-max-lag is set to 10. + +# A Redis master is able to list the address and port of the attached +# replicas in different ways. For example the "INFO replication" section +# offers this information, which is used, among other tools, by +# Redis Sentinel in order to discover replica instances. +# Another place where this info is available is in the output of the +# "ROLE" command of a master. +# +# The listed IP address and port normally reported by a replica is +# obtained in the following way: +# +# IP: The address is auto detected by checking the peer address +# of the socket used by the replica to connect with the master. +# +# Port: The port is communicated by the replica during the replication +# handshake, and is normally the port that the replica is using to +# listen for connections. +# +# However when port forwarding or Network Address Translation (NAT) is +# used, the replica may actually be reachable via different IP and port +# pairs. The following two options can be used by a replica in order to +# report to its master a specific set of IP and port, so that both INFO +# and ROLE will report those values. +# +# There is no need to use both the options if you need to override just +# the port or the IP address. +# +# replica-announce-ip 5.5.5.5 +# replica-announce-port 1234 + +############################### KEYS TRACKING ################################# + +# Redis implements server assisted support for client side caching of values. +# This is implemented using an invalidation table that remembers, using +# a radix key indexed by key name, what clients have which keys. In turn +# this is used in order to send invalidation messages to clients. Please +# check this page to understand more about the feature: +# +# https://redis.io/topics/client-side-caching +# +# When tracking is enabled for a client, all the read only queries are assumed +# to be cached: this will force Redis to store information in the invalidation +# table. When keys are modified, such information is flushed away, and +# invalidation messages are sent to the clients. However if the workload is +# heavily dominated by reads, Redis could use more and more memory in order +# to track the keys fetched by many clients. +# +# For this reason it is possible to configure a maximum fill value for the +# invalidation table. By default it is set to 1M of keys, and once this limit +# is reached, Redis will start to evict keys in the invalidation table +# even if they were not modified, just to reclaim memory: this will in turn +# force the clients to invalidate the cached values. Basically the table +# maximum size is a trade off between the memory you want to spend server +# side to track information about who cached what, and the ability of clients +# to retain cached objects in memory. +# +# If you set the value to 0, it means there are no limits, and Redis will +# retain as many keys as needed in the invalidation table. +# In the "stats" INFO section, you can find information about the number of +# keys in the invalidation table at every given moment. +# +# Note: when key tracking is used in broadcasting mode, no memory is used +# in the server side so this setting is useless. +# +# tracking-table-max-keys 1000000 + +################################## SECURITY ################################### + +# Warning: since Redis is pretty fast, an outside user can try up to +# 1 million passwords per second against a modern box. This means that you +# should use very strong passwords, otherwise they will be very easy to break. +# Note that because the password is really a shared secret between the client +# and the server, and should not be memorized by any human, the password +# can be easily a long string from /dev/urandom or whatever, so by using a +# long and unguessable password no brute force attack will be possible. + +# Redis ACL users are defined in the following format: +# +# user ... acl rules ... +# +# For example: +# +# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99 +# +# The special username "default" is used for new connections. If this user +# has the "nopass" rule, then new connections will be immediately authenticated +# as the "default" user without the need of any password provided via the +# AUTH command. Otherwise if the "default" user is not flagged with "nopass" +# the connections will start in not authenticated state, and will require +# AUTH (or the HELLO command AUTH option) in order to be authenticated and +# start to work. +# +# The ACL rules that describe what a user can do are the following: +# +# on Enable the user: it is possible to authenticate as this user. +# off Disable the user: it's no longer possible to authenticate +# with this user, however the already authenticated connections +# will still work. +# skip-sanitize-payload RESTORE dump-payload sanitization is skipped. +# sanitize-payload RESTORE dump-payload is sanitized (default). +# + Allow the execution of that command. +# May be used with `|` for allowing subcommands (e.g "+config|get") +# - Disallow the execution of that command. +# May be used with `|` for blocking subcommands (e.g "-config|set") +# +@ Allow the execution of all the commands in such category +# with valid categories are like @admin, @set, @sortedset, ... +# and so forth, see the full list in the server.c file where +# the Redis command table is described and defined. +# The special category @all means all the commands, but currently +# present in the server, and that will be loaded in the future +# via modules. +# +|first-arg Allow a specific first argument of an otherwise +# disabled command. It is only supported on commands with +# no sub-commands, and is not allowed as negative form +# like -SELECT|1, only additive starting with "+". This +# feature is deprecated and may be removed in the future. +# allcommands Alias for +@all. Note that it implies the ability to execute +# all the future commands loaded via the modules system. +# nocommands Alias for -@all. +# ~ Add a pattern of keys that can be mentioned as part of +# commands. For instance ~* allows all the keys. The pattern +# is a glob-style pattern like the one of KEYS. +# It is possible to specify multiple patterns. +# %R~ Add key read pattern that specifies which keys can be read +# from. +# %W~ Add key write pattern that specifies which keys can be +# written to. +# allkeys Alias for ~* +# resetkeys Flush the list of allowed keys patterns. +# & Add a glob-style pattern of Pub/Sub channels that can be +# accessed by the user. It is possible to specify multiple channel +# patterns. +# allchannels Alias for &* +# resetchannels Flush the list of allowed channel patterns. +# > Add this password to the list of valid password for the user. +# For example >mypass will add "mypass" to the list. +# This directive clears the "nopass" flag (see later). +# < Remove this password from the list of valid passwords. +# nopass All the set passwords of the user are removed, and the user +# is flagged as requiring no password: it means that every +# password will work against this user. If this directive is +# used for the default user, every new connection will be +# immediately authenticated with the default user without +# any explicit AUTH command required. Note that the "resetpass" +# directive will clear this condition. +# resetpass Flush the list of allowed passwords. Moreover removes the +# "nopass" status. After "resetpass" the user has no associated +# passwords and there is no way to authenticate without adding +# some password (or setting it as "nopass" later). +# reset Performs the following actions: resetpass, resetkeys, off, +# -@all. The user returns to the same state it has immediately +# after its creation. +# () Create a new selector with the options specified within the +# parentheses and attach it to the user. Each option should be +# space separated. The first character must be ( and the last +# character must be ). +# clearselectors Remove all of the currently attached selectors. +# Note this does not change the "root" user permissions, +# which are the permissions directly applied onto the +# user (outside the parentheses). +# +# ACL rules can be specified in any order: for instance you can start with +# passwords, then flags, or key patterns. However note that the additive +# and subtractive rules will CHANGE MEANING depending on the ordering. +# For instance see the following example: +# +# user alice on +@all -DEBUG ~* >somepassword +# +# This will allow "alice" to use all the commands with the exception of the +# DEBUG command, since +@all added all the commands to the set of the commands +# alice can use, and later DEBUG was removed. However if we invert the order +# of two ACL rules the result will be different: +# +# user alice on -DEBUG +@all ~* >somepassword +# +# Now DEBUG was removed when alice had yet no commands in the set of allowed +# commands, later all the commands are added, so the user will be able to +# execute everything. +# +# Basically ACL rules are processed left-to-right. +# +# The following is a list of command categories and their meanings: +# * keyspace - Writing or reading from keys, databases, or their metadata +# in a type agnostic way. Includes DEL, RESTORE, DUMP, RENAME, EXISTS, DBSIZE, +# KEYS, EXPIRE, TTL, FLUSHALL, etc. Commands that may modify the keyspace, +# key or metadata will also have `write` category. Commands that only read +# the keyspace, key or metadata will have the `read` category. +# * read - Reading from keys (values or metadata). Note that commands that don't +# interact with keys, will not have either `read` or `write`. +# * write - Writing to keys (values or metadata) +# * admin - Administrative commands. Normal applications will never need to use +# these. Includes REPLICAOF, CONFIG, DEBUG, SAVE, MONITOR, ACL, SHUTDOWN, etc. +# * dangerous - Potentially dangerous (each should be considered with care for +# various reasons). This includes FLUSHALL, MIGRATE, RESTORE, SORT, KEYS, +# CLIENT, DEBUG, INFO, CONFIG, SAVE, REPLICAOF, etc. +# * connection - Commands affecting the connection or other connections. +# This includes AUTH, SELECT, COMMAND, CLIENT, ECHO, PING, etc. +# * blocking - Potentially blocking the connection until released by another +# command. +# * fast - Fast O(1) commands. May loop on the number of arguments, but not the +# number of elements in the key. +# * slow - All commands that are not Fast. +# * pubsub - PUBLISH / SUBSCRIBE related +# * transaction - WATCH / MULTI / EXEC related commands. +# * scripting - Scripting related. +# * set - Data type: sets related. +# * sortedset - Data type: zsets related. +# * list - Data type: lists related. +# * hash - Data type: hashes related. +# * string - Data type: strings related. +# * bitmap - Data type: bitmaps related. +# * hyperloglog - Data type: hyperloglog related. +# * geo - Data type: geo related. +# * stream - Data type: streams related. +# +# For more information about ACL configuration please refer to +# the Redis web site at https://redis.io/topics/acl + +# ACL LOG +# +# The ACL Log tracks failed commands and authentication events associated +# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked +# by ACLs. The ACL Log is stored in memory. You can reclaim memory with +# ACL LOG RESET. Define the maximum entry length of the ACL Log below. +acllog-max-len 128 + +# Using an external ACL file +# +# Instead of configuring users here in this file, it is possible to use +# a stand-alone file just listing users. The two methods cannot be mixed: +# if you configure users here and at the same time you activate the external +# ACL file, the server will refuse to start. +# +# The format of the external ACL user file is exactly the same as the +# format that is used inside redis.conf to describe users. +# +# aclfile /etc/redis/users.acl + +# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility +# layer on top of the new ACL system. The option effect will be just setting +# the password for the default user. Clients will still authenticate using +# AUTH as usually, or more explicitly with AUTH default +# if they follow the new protocol: both will work. +# +# The requirepass is not compatible with aclfile option and the ACL LOAD +# command, these will cause requirepass to be ignored. +# +# requirepass foobared + +# New users are initialized with restrictive permissions by default, via the +# equivalent of this ACL rule 'off resetkeys -@all'. Starting with Redis 6.2, it +# is possible to manage access to Pub/Sub channels with ACL rules as well. The +# default Pub/Sub channels permission if new users is controlled by the +# acl-pubsub-default configuration directive, which accepts one of these values: +# +# allchannels: grants access to all Pub/Sub channels +# resetchannels: revokes access to all Pub/Sub channels +# +# From Redis 7.0, acl-pubsub-default defaults to 'resetchannels' permission. +# +# acl-pubsub-default resetchannels + +# Command renaming (DEPRECATED). +# +# ------------------------------------------------------------------------ +# WARNING: avoid using this option if possible. Instead use ACLs to remove +# commands from the default user, and put them only in some admin user you +# create for administrative purposes. +# ------------------------------------------------------------------------ +# +# It is possible to change the name of dangerous commands in a shared +# environment. For instance the CONFIG command may be renamed into something +# hard to guess so that it will still be available for internal-use tools +# but not available for general clients. +# +# Example: +# +# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 +# +# It is also possible to completely kill a command by renaming it into +# an empty string: +# +# rename-command CONFIG "" +# +# Please note that changing the name of commands that are logged into the +# AOF file or transmitted to replicas may cause problems. + +################################### CLIENTS #################################### + +# Set the max number of connected clients at the same time. By default +# this limit is set to 10000 clients, however if the Redis server is not +# able to configure the process file limit to allow for the specified limit +# the max number of allowed clients is set to the current file limit +# minus 32 (as Redis reserves a few file descriptors for internal uses). +# +# Once the limit is reached Redis will close all the new connections sending +# an error 'max number of clients reached'. +# +# IMPORTANT: When Redis Cluster is used, the max number of connections is also +# shared with the cluster bus: every node in the cluster will use two +# connections, one incoming and another outgoing. It is important to size the +# limit accordingly in case of very large clusters. +# +# maxclients 10000 + +############################## MEMORY MANAGEMENT ################################ + +# Set a memory usage limit to the specified amount of bytes. +# When the memory limit is reached Redis will try to remove keys +# according to the eviction policy selected (see maxmemory-policy). +# +# If Redis can't remove keys according to the policy, or if the policy is +# set to 'noeviction', Redis will start to reply with errors to commands +# that would use more memory, like SET, LPUSH, and so on, and will continue +# to reply to read-only commands like GET. +# +# This option is usually useful when using Redis as an LRU or LFU cache, or to +# set a hard memory limit for an instance (using the 'noeviction' policy). +# +# WARNING: If you have replicas attached to an instance with maxmemory on, +# the size of the output buffers needed to feed the replicas are subtracted +# from the used memory count, so that network problems / resyncs will +# not trigger a loop where keys are evicted, and in turn the output +# buffer of replicas is full with DELs of keys evicted triggering the deletion +# of more keys, and so forth until the database is completely emptied. +# +# In short... if you have replicas attached it is suggested that you set a lower +# limit for maxmemory so that there is some free RAM on the system for replica +# output buffers (but this is not needed if the policy is 'noeviction'). +# +# maxmemory + +# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory +# is reached. You can select one from the following behaviors: +# +# volatile-lru -> Evict using approximated LRU, only keys with an expire set. +# allkeys-lru -> Evict any key using approximated LRU. +# volatile-lfu -> Evict using approximated LFU, only keys with an expire set. +# allkeys-lfu -> Evict any key using approximated LFU. +# volatile-random -> Remove a random key having an expire set. +# allkeys-random -> Remove a random key, any key. +# volatile-ttl -> Remove the key with the nearest expire time (minor TTL) +# noeviction -> Don't evict anything, just return an error on write operations. +# +# LRU means Least Recently Used +# LFU means Least Frequently Used +# +# Both LRU, LFU and volatile-ttl are implemented using approximated +# randomized algorithms. +# +# Note: with any of the above policies, when there are no suitable keys for +# eviction, Redis will return an error on write operations that require +# more memory. These are usually commands that create new keys, add data or +# modify existing keys. A few examples are: SET, INCR, HSET, LPUSH, SUNIONSTORE, +# SORT (due to the STORE argument), and EXEC (if the transaction includes any +# command that requires memory). +# +# The default is: +# +# maxmemory-policy noeviction + +# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated +# algorithms (in order to save memory), so you can tune it for speed or +# accuracy. By default Redis will check five keys and pick the one that was +# used least recently, you can change the sample size using the following +# configuration directive. +# +# The default of 5 produces good enough results. 10 Approximates very closely +# true LRU but costs more CPU. 3 is faster but not very accurate. +# +# maxmemory-samples 5 + +# Eviction processing is designed to function well with the default setting. +# If there is an unusually large amount of write traffic, this value may need to +# be increased. Decreasing this value may reduce latency at the risk of +# eviction processing effectiveness +# 0 = minimum latency, 10 = default, 100 = process without regard to latency +# +# maxmemory-eviction-tenacity 10 + +# Starting from Redis 5, by default a replica will ignore its maxmemory setting +# (unless it is promoted to master after a failover or manually). It means +# that the eviction of keys will be just handled by the master, sending the +# DEL commands to the replica as keys evict in the master side. +# +# This behavior ensures that masters and replicas stay consistent, and is usually +# what you want, however if your replica is writable, or you want the replica +# to have a different memory setting, and you are sure all the writes performed +# to the replica are idempotent, then you may change this default (but be sure +# to understand what you are doing). +# +# Note that since the replica by default does not evict, it may end using more +# memory than the one set via maxmemory (there are certain buffers that may +# be larger on the replica, or data structures may sometimes take more memory +# and so forth). So make sure you monitor your replicas and make sure they +# have enough memory to never hit a real out-of-memory condition before the +# master hits the configured maxmemory setting. +# +# replica-ignore-maxmemory yes + +# Redis reclaims expired keys in two ways: upon access when those keys are +# found to be expired, and also in background, in what is called the +# "active expire key". The key space is slowly and interactively scanned +# looking for expired keys to reclaim, so that it is possible to free memory +# of keys that are expired and will never be accessed again in a short time. +# +# The default effort of the expire cycle will try to avoid having more than +# ten percent of expired keys still in memory, and will try to avoid consuming +# more than 25% of total memory and to add latency to the system. However +# it is possible to increase the expire "effort" that is normally set to +# "1", to a greater value, up to the value "10". At its maximum value the +# system will use more CPU, longer cycles (and technically may introduce +# more latency), and will tolerate less already expired keys still present +# in the system. It's a tradeoff between memory, CPU and latency. +# +# active-expire-effort 1 + +############################# LAZY FREEING #################################### + +# Redis has two primitives to delete keys. One is called DEL and is a blocking +# deletion of the object. It means that the server stops processing new commands +# in order to reclaim all the memory associated with an object in a synchronous +# way. If the key deleted is associated with a small object, the time needed +# in order to execute the DEL command is very small and comparable to most other +# O(1) or O(log_N) commands in Redis. However if the key is associated with an +# aggregated value containing millions of elements, the server can block for +# a long time (even seconds) in order to complete the operation. +# +# For the above reasons Redis also offers non blocking deletion primitives +# such as UNLINK (non blocking DEL) and the ASYNC option of FLUSHALL and +# FLUSHDB commands, in order to reclaim memory in background. Those commands +# are executed in constant time. Another thread will incrementally free the +# object in the background as fast as possible. +# +# DEL, UNLINK and ASYNC option of FLUSHALL and FLUSHDB are user-controlled. +# It's up to the design of the application to understand when it is a good +# idea to use one or the other. However the Redis server sometimes has to +# delete keys or flush the whole database as a side effect of other operations. +# Specifically Redis deletes objects independently of a user call in the +# following scenarios: +# +# 1) On eviction, because of the maxmemory and maxmemory policy configurations, +# in order to make room for new data, without going over the specified +# memory limit. +# 2) Because of expire: when a key with an associated time to live (see the +# EXPIRE command) must be deleted from memory. +# 3) Because of a side effect of a command that stores data on a key that may +# already exist. For example the RENAME command may delete the old key +# content when it is replaced with another one. Similarly SUNIONSTORE +# or SORT with STORE option may delete existing keys. The SET command +# itself removes any old content of the specified key in order to replace +# it with the specified string. +# 4) During replication, when a replica performs a full resynchronization with +# its master, the content of the whole database is removed in order to +# load the RDB file just transferred. +# +# In all the above cases the default is to delete objects in a blocking way, +# like if DEL was called. However you can configure each case specifically +# in order to instead release memory in a non-blocking way like if UNLINK +# was called, using the following configuration directives. + +lazyfree-lazy-eviction no +lazyfree-lazy-expire no +lazyfree-lazy-server-del no +replica-lazy-flush no + +# It is also possible, for the case when to replace the user code DEL calls +# with UNLINK calls is not easy, to modify the default behavior of the DEL +# command to act exactly like UNLINK, using the following configuration +# directive: + +lazyfree-lazy-user-del no + +# FLUSHDB, FLUSHALL, SCRIPT FLUSH and FUNCTION FLUSH support both asynchronous and synchronous +# deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the +# commands. When neither flag is passed, this directive will be used to determine +# if the data should be deleted asynchronously. + +lazyfree-lazy-user-flush no + +################################ THREADED I/O ################################# + +# Redis is mostly single threaded, however there are certain threaded +# operations such as UNLINK, slow I/O accesses and other things that are +# performed on side threads. +# +# Now it is also possible to handle Redis clients socket reads and writes +# in different I/O threads. Since especially writing is so slow, normally +# Redis users use pipelining in order to speed up the Redis performances per +# core, and spawn multiple instances in order to scale more. Using I/O +# threads it is possible to easily speedup two times Redis without resorting +# to pipelining nor sharding of the instance. +# +# By default threading is disabled, we suggest enabling it only in machines +# that have at least 4 or more cores, leaving at least one spare core. +# Using more than 8 threads is unlikely to help much. We also recommend using +# threaded I/O only if you actually have performance problems, with Redis +# instances being able to use a quite big percentage of CPU time, otherwise +# there is no point in using this feature. +# +# So for instance if you have a four cores boxes, try to use 2 or 3 I/O +# threads, if you have a 8 cores, try to use 6 threads. In order to +# enable I/O threads use the following configuration directive: +# +# io-threads 4 +# +# Setting io-threads to 1 will just use the main thread as usual. +# When I/O threads are enabled, we only use threads for writes, that is +# to thread the write(2) syscall and transfer the client buffers to the +# socket. However it is also possible to enable threading of reads and +# protocol parsing using the following configuration directive, by setting +# it to yes: +# +# io-threads-do-reads no +# +# Usually threading reads doesn't help much. +# +# NOTE 1: This configuration directive cannot be changed at runtime via +# CONFIG SET. Also, this feature currently does not work when SSL is +# enabled. +# +# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make +# sure you also run the benchmark itself in threaded mode, using the +# --threads option to match the number of Redis threads, otherwise you'll not +# be able to notice the improvements. + +############################ KERNEL OOM CONTROL ############################## + +# On Linux, it is possible to hint the kernel OOM killer on what processes +# should be killed first when out of memory. +# +# Enabling this feature makes Redis actively control the oom_score_adj value +# for all its processes, depending on their role. The default scores will +# attempt to have background child processes killed before all others, and +# replicas killed before masters. +# +# Redis supports these options: +# +# no: Don't make changes to oom-score-adj (default). +# yes: Alias to "relative" see below. +# absolute: Values in oom-score-adj-values are written as is to the kernel. +# relative: Values are used relative to the initial value of oom_score_adj when +# the server starts and are then clamped to a range of -1000 to 1000. +# Because typically the initial value is 0, they will often match the +# absolute values. +oom-score-adj no + +# When oom-score-adj is used, this directive controls the specific values used +# for master, replica and background child processes. Values range -2000 to +# 2000 (higher means more likely to be killed). +# +# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities) +# can freely increase their value, but not decrease it below its initial +# settings. This means that setting oom-score-adj to "relative" and setting the +# oom-score-adj-values to positive values will always succeed. +oom-score-adj-values 0 200 800 + + +#################### KERNEL transparent hugepage CONTROL ###################### + +# Usually the kernel Transparent Huge Pages control is set to "madvise" or +# or "never" by default (/sys/kernel/mm/transparent_hugepage/enabled), in which +# case this config has no effect. On systems in which it is set to "always", +# redis will attempt to disable it specifically for the redis process in order +# to avoid latency problems specifically with fork(2) and CoW. +# If for some reason you prefer to keep it enabled, you can set this config to +# "no" and the kernel global to "always". + +disable-thp yes + +############################## APPEND ONLY MODE ############################### + +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). +# +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. +# +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check https://redis.io/topics/persistence for more information. + +appendonly no + +# The base name of the append only file. +# +# Redis 7 and newer use a set of append-only files to persist the dataset +# and changes applied to it. There are two basic types of files in use: +# +# - Base files, which are a snapshot representing the complete state of the +# dataset at the time the file was created. Base files can be either in +# the form of RDB (binary serialized) or AOF (textual commands). +# - Incremental files, which contain additional commands that were applied +# to the dataset following the previous file. +# +# In addition, manifest files are used to track the files and the order in +# which they were created and should be applied. +# +# Append-only file names are created by Redis following a specific pattern. +# The file name's prefix is based on the 'appendfilename' configuration +# parameter, followed by additional information about the sequence and type. +# +# For example, if appendfilename is set to appendonly.aof, the following file +# names could be derived: +# +# - appendonly.aof.1.base.rdb as a base file. +# - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files. +# - appendonly.aof.manifest as a manifest file. + +appendfilename "appendonly.aof" + +# For convenience, Redis stores all persistent append-only files in a dedicated +# directory. The name of the directory is determined by the appenddirname +# configuration parameter. + +appenddirname "appendonlydir" + +# The fsync() call tells the Operating System to actually write data on disk +# instead of waiting for more data in the output buffer. Some OS will really flush +# data on disk, some other OS will just try to do it ASAP. +# +# Redis supports three different modes: +# +# no: don't fsync, just let the OS flush the data when it wants. Faster. +# always: fsync after every write to the append only log. Slow, Safest. +# everysec: fsync only one time every second. Compromise. +# +# The default is "everysec", as that's usually the right compromise between +# speed and data safety. It's up to you to understand if you can relax this to +# "no" that will let the operating system flush the output buffer when +# it wants, for better performances (but if you can live with the idea of +# some data loss consider the default persistence mode that's snapshotting), +# or on the contrary, use "always" that's very slow but a bit safer than +# everysec. +# +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# +# If unsure, use "everysec". + +# appendfsync always +appendfsync everysec +# appendfsync no + +# When the AOF fsync policy is set to always or everysec, and a background +# saving process (a background save or AOF log background rewriting) is +# performing a lot of I/O against the disk, in some Linux configurations +# Redis may block too long on the fsync() call. Note that there is no fix for +# this currently, as even performing fsync in a different thread will block +# our synchronous write(2) call. +# +# In order to mitigate this problem it's possible to use the following option +# that will prevent fsync() from being called in the main process while a +# BGSAVE or BGREWRITEAOF is in progress. +# +# This means that while another child is saving, the durability of Redis is +# the same as "appendfsync no". In practical terms, this means that it is +# possible to lose up to 30 seconds of log in the worst scenario (with the +# default Linux settings). +# +# If you have latency problems turn this to "yes". Otherwise leave it as +# "no" that is the safest pick from the point of view of durability. + +no-appendfsync-on-rewrite no + +# Automatic rewrite of the append only file. +# Redis is able to automatically rewrite the log file implicitly calling +# BGREWRITEAOF when the AOF log size grows by the specified percentage. +# +# This is how it works: Redis remembers the size of the AOF file after the +# latest rewrite (if no rewrite has happened since the restart, the size of +# the AOF at startup is used). +# +# This base size is compared to the current size. If the current size is +# bigger than the specified percentage, the rewrite is triggered. Also +# you need to specify a minimal size for the AOF file to be rewritten, this +# is useful to avoid rewriting the AOF file even if the percentage increase +# is reached but it is still pretty small. +# +# Specify a percentage of zero in order to disable the automatic AOF +# rewrite feature. + +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + +# An AOF file may be found to be truncated at the end during the Redis +# startup process, when the AOF data gets loaded back into memory. +# This may happen when the system where Redis is running +# crashes, especially when an ext4 filesystem is mounted without the +# data=ordered option (however this can't happen when Redis itself +# crashes or aborts but the operating system still works correctly). +# +# Redis can either exit with an error when this happens, or load as much +# data as possible (the default now) and start if the AOF file is found +# to be truncated at the end. The following option controls this behavior. +# +# If aof-load-truncated is set to yes, a truncated AOF file is loaded and +# the Redis server starts emitting a log to inform the user of the event. +# Otherwise if the option is set to no, the server aborts with an error +# and refuses to start. When the option is set to no, the user requires +# to fix the AOF file using the "redis-check-aof" utility before to restart +# the server. +# +# Note that if the AOF file will be found to be corrupted in the middle +# the server will still exit with an error. This option only applies when +# Redis will try to read more data from the AOF file but not enough bytes +# will be found. +aof-load-truncated yes + +# Redis can create append-only base files in either RDB or AOF formats. Using +# the RDB format is always faster and more efficient, and disabling it is only +# supported for backward compatibility purposes. +aof-use-rdb-preamble yes + +# Redis supports recording timestamp annotations in the AOF to support restoring +# the data from a specific point-in-time. However, using this capability changes +# the AOF format in a way that may not be compatible with existing AOF parsers. +aof-timestamp-enabled no + +################################ SHUTDOWN ##################################### + +# Maximum time to wait for replicas when shutting down, in seconds. +# +# During shut down, a grace period allows any lagging replicas to catch up with +# the latest replication offset before the master exists. This period can +# prevent data loss, especially for deployments without configured disk backups. +# +# The 'shutdown-timeout' value is the grace period's duration in seconds. It is +# only applicable when the instance has replicas. To disable the feature, set +# the value to 0. +# +# shutdown-timeout 10 + +# When Redis receives a SIGINT or SIGTERM, shutdown is initiated and by default +# an RDB snapshot is written to disk in a blocking operation if save points are configured. +# The options used on signaled shutdown can include the following values: +# default: Saves RDB snapshot only if save points are configured. +# Waits for lagging replicas to catch up. +# save: Forces a DB saving operation even if no save points are configured. +# nosave: Prevents DB saving operation even if one or more save points are configured. +# now: Skips waiting for lagging replicas. +# force: Ignores any errors that would normally prevent the server from exiting. +# +# Any combination of values is allowed as long as "save" and "nosave" are not set simultaneously. +# Example: "nosave force now" +# +# shutdown-on-sigint default +# shutdown-on-sigterm default + +################ NON-DETERMINISTIC LONG BLOCKING COMMANDS ##################### + +# Maximum time in milliseconds for EVAL scripts, functions and in some cases +# modules' commands before Redis can start processing or rejecting other clients. +# +# If the maximum execution time is reached Redis will start to reply to most +# commands with a BUSY error. +# +# In this state Redis will only allow a handful of commands to be executed. +# For instance, SCRIPT KILL, FUNCTION KILL, SHUTDOWN NOSAVE and possibly some +# module specific 'allow-busy' commands. +# +# SCRIPT KILL and FUNCTION KILL will only be able to stop a script that did not +# yet call any write commands, so SHUTDOWN NOSAVE may be the only way to stop +# the server in the case a write command was already issued by the script when +# the user doesn't want to wait for the natural termination of the script. +# +# The default is 5 seconds. It is possible to set it to 0 or a negative value +# to disable this mechanism (uninterrupted execution). Note that in the past +# this config had a different name, which is now an alias, so both of these do +# the same: +# lua-time-limit 5000 +# busy-reply-threshold 5000 + +################################ REDIS CLUSTER ############################### + +# Normal Redis instances can't be part of a Redis Cluster; only nodes that are +# started as cluster nodes can. In order to start a Redis instance as a +# cluster node enable the cluster support uncommenting the following: +# +# cluster-enabled yes + +# Every cluster node has a cluster configuration file. This file is not +# intended to be edited by hand. It is created and updated by Redis nodes. +# Every Redis Cluster node requires a different cluster configuration file. +# Make sure that instances running in the same system do not have +# overlapping cluster configuration file names. +# +# cluster-config-file nodes-6379.conf + +# Cluster node timeout is the amount of milliseconds a node must be unreachable +# for it to be considered in failure state. +# Most other internal time limits are a multiple of the node timeout. +# +# cluster-node-timeout 15000 + +# The cluster port is the port that the cluster bus will listen for inbound connections on. When set +# to the default value, 0, it will be bound to the command port + 10000. Setting this value requires +# you to specify the cluster bus port when executing cluster meet. +# cluster-port 0 + +# A replica of a failing master will avoid to start a failover if its data +# looks too old. +# +# There is no simple way for a replica to actually have an exact measure of +# its "data age", so the following two checks are performed: +# +# 1) If there are multiple replicas able to failover, they exchange messages +# in order to try to give an advantage to the replica with the best +# replication offset (more data from the master processed). +# Replicas will try to get their rank by offset, and apply to the start +# of the failover a delay proportional to their rank. +# +# 2) Every single replica computes the time of the last interaction with +# its master. This can be the last ping or command received (if the master +# is still in the "connected" state), or the time that elapsed since the +# disconnection with the master (if the replication link is currently down). +# If the last interaction is too old, the replica will not try to failover +# at all. +# +# The point "2" can be tuned by user. Specifically a replica will not perform +# the failover if, since the last interaction with the master, the time +# elapsed is greater than: +# +# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period +# +# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor +# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the +# replica will not try to failover if it was not able to talk with the master +# for longer than 310 seconds. +# +# A large cluster-replica-validity-factor may allow replicas with too old data to failover +# a master, while a too small value may prevent the cluster from being able to +# elect a replica at all. +# +# For maximum availability, it is possible to set the cluster-replica-validity-factor +# to a value of 0, which means, that replicas will always try to failover the +# master regardless of the last time they interacted with the master. +# (However they'll always try to apply a delay proportional to their +# offset rank). +# +# Zero is the only value able to guarantee that when all the partitions heal +# the cluster will always be able to continue. +# +# cluster-replica-validity-factor 10 + +# Cluster replicas are able to migrate to orphaned masters, that are masters +# that are left without working replicas. This improves the cluster ability +# to resist to failures as otherwise an orphaned master can't be failed over +# in case of failure if it has no working replicas. +# +# Replicas migrate to orphaned masters only if there are still at least a +# given number of other working replicas for their old master. This number +# is the "migration barrier". A migration barrier of 1 means that a replica +# will migrate only if there is at least 1 other working replica for its master +# and so forth. It usually reflects the number of replicas you want for every +# master in your cluster. +# +# Default is 1 (replicas migrate only if their masters remain with at least +# one replica). To disable migration just set it to a very large value or +# set cluster-allow-replica-migration to 'no'. +# A value of 0 can be set but is useful only for debugging and dangerous +# in production. +# +# cluster-migration-barrier 1 + +# Turning off this option allows to use less automatic cluster configuration. +# It both disables migration to orphaned masters and migration from masters +# that became empty. +# +# Default is 'yes' (allow automatic migrations). +# +# cluster-allow-replica-migration yes + +# By default Redis Cluster nodes stop accepting queries if they detect there +# is at least a hash slot uncovered (no available node is serving it). +# This way if the cluster is partially down (for example a range of hash slots +# are no longer covered) all the cluster becomes, eventually, unavailable. +# It automatically returns available as soon as all the slots are covered again. +# +# However sometimes you want the subset of the cluster which is working, +# to continue to accept queries for the part of the key space that is still +# covered. In order to do so, just set the cluster-require-full-coverage +# option to no. +# +# cluster-require-full-coverage yes + +# This option, when set to yes, prevents replicas from trying to failover its +# master during master failures. However the replica can still perform a +# manual failover, if forced to do so. +# +# This is useful in different scenarios, especially in the case of multiple +# data center operations, where we want one side to never be promoted if not +# in the case of a total DC failure. +# +# cluster-replica-no-failover no + +# This option, when set to yes, allows nodes to serve read traffic while the +# cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful for two cases. The first case is for when an application +# doesn't require consistency of data during node failures or network partitions. +# One example of this is a cache, where as long as the node has the data it +# should be able to serve it. +# +# The second use case is for configurations that don't meet the recommended +# three shards but want to enable cluster mode and scale later. A +# master outage in a 1 or 2 shard configuration causes a read/write outage to the +# entire cluster without this option set, with it set there is only a write outage. +# Without a quorum of masters, slot ownership will not change automatically. +# +# cluster-allow-reads-when-down no + +# This option, when set to yes, allows nodes to serve pubsub shard traffic while +# the cluster is in a down state, as long as it believes it owns the slots. +# +# This is useful if the application would like to use the pubsub feature even when +# the cluster global stable state is not OK. If the application wants to make sure only +# one shard is serving a given channel, this feature should be kept as yes. +# +# cluster-allow-pubsubshard-when-down yes + +# Cluster link send buffer limit is the limit on the memory usage of an individual +# cluster bus link's send buffer in bytes. Cluster links would be freed if they exceed +# this limit. This is to primarily prevent send buffers from growing unbounded on links +# toward slow peers (E.g. PubSub messages being piled up). +# This limit is disabled by default. Enable this limit when 'mem_cluster_links' INFO field +# and/or 'send-buffer-allocated' entries in the 'CLUSTER LINKS` command output continuously increase. +# Minimum limit of 1gb is recommended so that cluster link buffer can fit in at least a single +# PubSub message by default. (client-query-buffer-limit default value is 1gb) +# +# cluster-link-sendbuf-limit 0 + +# Clusters can configure their announced hostname using this config. This is a common use case for +# applications that need to use TLS Server Name Indication (SNI) or dealing with DNS based +# routing. By default this value is only shown as additional metadata in the CLUSTER SLOTS +# command, but can be changed using 'cluster-preferred-endpoint-type' config. This value is +# communicated along the clusterbus to all nodes, setting it to an empty string will remove +# the hostname and also propagate the removal. +# +# cluster-announce-hostname "" + +# Clusters can advertise how clients should connect to them using either their IP address, +# a user defined hostname, or by declaring they have no endpoint. Which endpoint is +# shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type +# config with values 'ip', 'hostname', or 'unknown-endpoint'. This value controls how +# the endpoint returned for MOVED/ASKING requests as well as the first field of CLUSTER SLOTS. +# If the preferred endpoint type is set to hostname, but no announced hostname is set, a '?' +# will be returned instead. +# +# When a cluster advertises itself as having an unknown endpoint, it's indicating that +# the server doesn't know how clients can reach the cluster. This can happen in certain +# networking situations where there are multiple possible routes to the node, and the +# server doesn't know which one the client took. In this case, the server is expecting +# the client to reach out on the same endpoint it used for making the last request, but use +# the port provided in the response. +# +# cluster-preferred-endpoint-type ip + +# In order to setup your cluster make sure to read the documentation +# available at https://redis.io web site. + +########################## CLUSTER DOCKER/NAT support ######################## + +# In certain deployments, Redis Cluster nodes address discovery fails, because +# addresses are NAT-ted or because ports are forwarded (the typical case is +# Docker and other containers). +# +# In order to make Redis Cluster working in such environments, a static +# configuration where each node knows its public address is needed. The +# following four options are used for this scope, and are: +# +# * cluster-announce-ip +# * cluster-announce-port +# * cluster-announce-tls-port +# * cluster-announce-bus-port +# +# Each instructs the node about its address, client ports (for connections +# without and with TLS) and cluster message bus port. The information is then +# published in the header of the bus packets so that other nodes will be able to +# correctly map the address of the node publishing the information. +# +# If cluster-tls is set to yes and cluster-announce-tls-port is omitted or set +# to zero, then cluster-announce-port refers to the TLS port. Note also that +# cluster-announce-tls-port has no effect if cluster-tls is set to no. +# +# If the above options are not used, the normal Redis Cluster auto-detection +# will be used instead. +# +# Note that when remapped, the bus port may not be at the fixed offset of +# clients port + 10000, so you can specify any port and bus-port depending +# on how they get remapped. If the bus-port is not set, a fixed offset of +# 10000 will be used as usual. +# +# Example: +# +# cluster-announce-ip 10.1.1.5 +# cluster-announce-tls-port 6379 +# cluster-announce-port 0 +# cluster-announce-bus-port 6380 + +################################## SLOW LOG ################################### + +# The Redis Slow Log is a system to log queries that exceeded a specified +# execution time. The execution time does not include the I/O operations +# like talking with the client, sending the reply and so forth, +# but just the time needed to actually execute the command (this is the only +# stage of command execution where the thread is blocked and can not serve +# other requests in the meantime). +# +# You can configure the slow log with two parameters: one tells Redis +# what is the execution time, in microseconds, to exceed in order for the +# command to get logged, and the other parameter is the length of the +# slow log. When a new command is logged the oldest one is removed from the +# queue of logged commands. + +# The following time is expressed in microseconds, so 1000000 is equivalent +# to one second. Note that a negative number disables the slow log, while +# a value of zero forces the logging of every command. +slowlog-log-slower-than 10000 + +# There is no limit to this length. Just be aware that it will consume memory. +# You can reclaim memory used by the slow log with SLOWLOG RESET. +slowlog-max-len 128 + +################################ LATENCY MONITOR ############################## + +# The Redis latency monitoring subsystem samples different operations +# at runtime in order to collect data related to possible sources of +# latency of a Redis instance. +# +# Via the LATENCY command this information is available to the user that can +# print graphs and obtain reports. +# +# The system only logs operations that were performed in a time equal or +# greater than the amount of milliseconds specified via the +# latency-monitor-threshold configuration directive. When its value is set +# to zero, the latency monitor is turned off. +# +# By default latency monitoring is disabled since it is mostly not needed +# if you don't have latency issues, and collecting data has a performance +# impact, that while very small, can be measured under big load. Latency +# monitoring can easily be enabled at runtime using the command +# "CONFIG SET latency-monitor-threshold " if needed. +latency-monitor-threshold 0 + +################################ LATENCY TRACKING ############################## + +# The Redis extended latency monitoring tracks the per command latencies and enables +# exporting the percentile distribution via the INFO latencystats command, +# and cumulative latency distributions (histograms) via the LATENCY command. +# +# By default, the extended latency monitoring is enabled since the overhead +# of keeping track of the command latency is very small. +# latency-tracking yes + +# By default the exported latency percentiles via the INFO latencystats command +# are the p50, p99, and p999. +# latency-tracking-info-percentiles 50 99 99.9 + +############################# EVENT NOTIFICATION ############################## + +# Redis can notify Pub/Sub clients about events happening in the key space. +# This feature is documented at https://redis.io/topics/notifications +# +# For instance if keyspace events notification is enabled, and a client +# performs a DEL operation on key "foo" stored in the Database 0, two +# messages will be published via Pub/Sub: +# +# PUBLISH __keyspace@0__:foo del +# PUBLISH __keyevent@0__:del foo +# +# It is possible to select the events that Redis will notify among a set +# of classes. Every class is identified by a single character: +# +# K Keyspace events, published with __keyspace@__ prefix. +# E Keyevent events, published with __keyevent@__ prefix. +# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... +# $ String commands +# l List commands +# s Set commands +# h Hash commands +# z Sorted set commands +# x Expired events (events generated every time a key expires) +# e Evicted events (events generated when a key is evicted for maxmemory) +# n New key events (Note: not included in the 'A' class) +# t Stream commands +# d Module key type events +# m Key-miss events (Note: It is not included in the 'A' class) +# A Alias for g$lshzxetd, so that the "AKE" string means all the events +# (Except key-miss events which are excluded from 'A' due to their +# unique nature). +# +# The "notify-keyspace-events" takes as argument a string that is composed +# of zero or multiple characters. The empty string means that notifications +# are disabled. +# +# Example: to enable list and generic events, from the point of view of the +# event name, use: +# +# notify-keyspace-events Elg +# +# Example 2: to get the stream of the expired keys subscribing to channel +# name __keyevent@0__:expired use: +# +# notify-keyspace-events Ex +# +# By default all notifications are disabled because most users don't need +# this feature and the feature has some overhead. Note that if you don't +# specify at least one of K or E, no events will be delivered. +notify-keyspace-events "" + +############################### ADVANCED CONFIG ############################### + +# Hashes are encoded using a memory efficient data structure when they have a +# small number of entries, and the biggest entry does not exceed a given +# threshold. These thresholds can be configured using the following directives. +hash-max-listpack-entries 512 +hash-max-listpack-value 64 + +# Lists are also encoded in a special way to save a lot of space. +# The number of entries allowed per internal list node can be specified +# as a fixed maximum size or a maximum number of elements. +# For a fixed maximum size, use -5 through -1, meaning: +# -5: max size: 64 Kb <-- not recommended for normal workloads +# -4: max size: 32 Kb <-- not recommended +# -3: max size: 16 Kb <-- probably not recommended +# -2: max size: 8 Kb <-- good +# -1: max size: 4 Kb <-- good +# Positive numbers mean store up to _exactly_ that number of elements +# per list node. +# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), +# but if your use case is unique, adjust the settings as necessary. +list-max-listpack-size -2 + +# Lists may also be compressed. +# Compress depth is the number of quicklist ziplist nodes from *each* side of +# the list to *exclude* from compression. The head and tail of the list +# are always uncompressed for fast push/pop operations. Settings are: +# 0: disable all list compression +# 1: depth 1 means "don't start compressing until after 1 node into the list, +# going from either the head or tail" +# So: [head]->node->node->...->node->[tail] +# [head], [tail] will always be uncompressed; inner nodes will compress. +# 2: [head]->[next]->node->node->...->node->[prev]->[tail] +# 2 here means: don't compress head or head->next or tail->prev or tail, +# but compress all nodes between them. +# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail] +# etc. +list-compress-depth 0 + +# Sets have a special encoding in just one case: when a set is composed +# of just strings that happen to be integers in radix 10 in the range +# of 64 bit signed integers. +# The following configuration setting sets the limit in the size of the +# set in order to use this special memory saving encoding. +set-max-intset-entries 512 + +# Similarly to hashes and lists, sorted sets are also specially encoded in +# order to save a lot of space. This encoding is only used when the length and +# elements of a sorted set are below the following limits: +zset-max-listpack-entries 128 +zset-max-listpack-value 64 + +# HyperLogLog sparse representation bytes limit. The limit includes the +# 16 bytes header. When an HyperLogLog using the sparse representation crosses +# this limit, it is converted into the dense representation. +# +# A value greater than 16000 is totally useless, since at that point the +# dense representation is more memory efficient. +# +# The suggested value is ~ 3000 in order to have the benefits of +# the space efficient encoding without slowing down too much PFADD, +# which is O(N) with the sparse encoding. The value can be raised to +# ~ 10000 when CPU is not a concern, but space is, and the data set is +# composed of many HyperLogLogs with cardinality in the 0 - 15000 range. +hll-sparse-max-bytes 3000 + +# Streams macro node max size / items. The stream data structure is a radix +# tree of big nodes that encode multiple items inside. Using this configuration +# it is possible to configure how big a single node can be in bytes, and the +# maximum number of items it may contain before switching to a new node when +# appending new stream entries. If any of the following settings are set to +# zero, the limit is ignored, so for instance it is possible to set just a +# max entries limit by setting max-bytes to 0 and max-entries to the desired +# value. +stream-node-max-bytes 4096 +stream-node-max-entries 100 + +# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in +# order to help rehashing the main Redis hash table (the one mapping top-level +# keys to values). The hash table implementation Redis uses (see dict.c) +# performs a lazy rehashing: the more operation you run into a hash table +# that is rehashing, the more rehashing "steps" are performed, so if the +# server is idle the rehashing is never complete and some more memory is used +# by the hash table. +# +# The default is to use this millisecond 10 times every second in order to +# actively rehash the main dictionaries, freeing memory when possible. +# +# If unsure: +# use "activerehashing no" if you have hard latency requirements and it is +# not a good thing in your environment that Redis can reply from time to time +# to queries with 2 milliseconds delay. +# +# use "activerehashing yes" if you don't have such hard requirements but +# want to free memory asap when possible. +activerehashing yes + +# The client output buffer limits can be used to force disconnection of clients +# that are not reading data from the server fast enough for some reason (a +# common reason is that a Pub/Sub client can't consume messages as fast as the +# publisher can produce them). +# +# The limit can be set differently for the three different classes of clients: +# +# normal -> normal clients including MONITOR clients +# replica -> replica clients +# pubsub -> clients subscribed to at least one pubsub channel or pattern +# +# The syntax of every client-output-buffer-limit directive is the following: +# +# client-output-buffer-limit +# +# A client is immediately disconnected once the hard limit is reached, or if +# the soft limit is reached and remains reached for the specified number of +# seconds (continuously). +# So for instance if the hard limit is 32 megabytes and the soft limit is +# 16 megabytes / 10 seconds, the client will get disconnected immediately +# if the size of the output buffers reach 32 megabytes, but will also get +# disconnected if the client reaches 16 megabytes and continuously overcomes +# the limit for 10 seconds. +# +# By default normal clients are not limited because they don't receive data +# without asking (in a push way), but just after a request, so only +# asynchronous clients may create a scenario where data is requested faster +# than it can read. +# +# Instead there is a default limit for pubsub and replica clients, since +# subscribers and replicas receive data in a push fashion. +# +# Note that it doesn't make sense to set the replica clients output buffer +# limit lower than the repl-backlog-size config (partial sync will succeed +# and then replica will get disconnected). +# Such a configuration is ignored (the size of repl-backlog-size will be used). +# This doesn't have memory consumption implications since the replica client +# will share the backlog buffers memory. +# +# Both the hard or the soft limit can be disabled by setting them to zero. +client-output-buffer-limit normal 0 0 0 +client-output-buffer-limit replica 256mb 64mb 60 +client-output-buffer-limit pubsub 32mb 8mb 60 + +# Client query buffers accumulate new commands. They are limited to a fixed +# amount by default in order to avoid that a protocol desynchronization (for +# instance due to a bug in the client) will lead to unbound memory usage in +# the query buffer. However you can configure it here if you have very special +# needs, such us huge multi/exec requests or alike. +# +# client-query-buffer-limit 1gb + +# In some scenarios client connections can hog up memory leading to OOM +# errors or data eviction. To avoid this we can cap the accumulated memory +# used by all client connections (all pubsub and normal clients). Once we +# reach that limit connections will be dropped by the server freeing up +# memory. The server will attempt to drop the connections using the most +# memory first. We call this mechanism "client eviction". +# +# Client eviction is configured using the maxmemory-clients setting as follows: +# 0 - client eviction is disabled (default) +# +# A memory value can be used for the client eviction threshold, +# for example: +# maxmemory-clients 1g +# +# A percentage value (between 1% and 100%) means the client eviction threshold +# is based on a percentage of the maxmemory setting. For example to set client +# eviction at 5% of maxmemory: +# maxmemory-clients 5% + +# In the Redis protocol, bulk requests, that are, elements representing single +# strings, are normally limited to 512 mb. However you can change this limit +# here, but must be 1mb or greater +# +# proto-max-bulk-len 512mb + +# Redis calls an internal function to perform many background tasks, like +# closing connections of clients in timeout, purging expired keys that are +# never requested, and so forth. +# +# Not all tasks are performed with the same frequency, but Redis checks for +# tasks to perform according to the specified "hz" value. +# +# By default "hz" is set to 10. Raising the value will use more CPU when +# Redis is idle, but at the same time will make Redis more responsive when +# there are many keys expiring at the same time, and timeouts may be +# handled with more precision. +# +# The range is between 1 and 500, however a value over 100 is usually not +# a good idea. Most users should use the default of 10 and raise this up to +# 100 only in environments where very low latency is required. +hz 10 + +# Normally it is useful to have an HZ value which is proportional to the +# number of clients connected. This is useful in order, for instance, to +# avoid too many clients are processed for each background task invocation +# in order to avoid latency spikes. +# +# Since the default HZ value by default is conservatively set to 10, Redis +# offers, and enables by default, the ability to use an adaptive HZ value +# which will temporarily raise when there are many connected clients. +# +# When dynamic HZ is enabled, the actual configured HZ will be used +# as a baseline, but multiples of the configured HZ value will be actually +# used as needed once more clients are connected. In this way an idle +# instance will use very little CPU time while a busy instance will be +# more responsive. +dynamic-hz yes + +# When a child rewrites the AOF file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +aof-rewrite-incremental-fsync yes + +# When redis saves RDB file, if the following option is enabled +# the file will be fsync-ed every 4 MB of data generated. This is useful +# in order to commit the file to the disk more incrementally and avoid +# big latency spikes. +rdb-save-incremental-fsync yes + +# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good +# idea to start with the default settings and only change them after investigating +# how to improve the performances and how the keys LFU change over time, which +# is possible to inspect via the OBJECT FREQ command. +# +# There are two tunable parameters in the Redis LFU implementation: the +# counter logarithm factor and the counter decay time. It is important to +# understand what the two parameters mean before changing them. +# +# The LFU counter is just 8 bits per key, it's maximum value is 255, so Redis +# uses a probabilistic increment with logarithmic behavior. Given the value +# of the old counter, when a key is accessed, the counter is incremented in +# this way: +# +# 1. A random number R between 0 and 1 is extracted. +# 2. A probability P is calculated as 1/(old_value*lfu_log_factor+1). +# 3. The counter is incremented only if R < P. +# +# The default lfu-log-factor is 10. This is a table of how the frequency +# counter changes with a different number of accesses with different +# logarithmic factors: +# +# +--------+------------+------------+------------+------------+------------+ +# | factor | 100 hits | 1000 hits | 100K hits | 1M hits | 10M hits | +# +--------+------------+------------+------------+------------+------------+ +# | 0 | 104 | 255 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 1 | 18 | 49 | 255 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 10 | 10 | 18 | 142 | 255 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# | 100 | 8 | 11 | 49 | 143 | 255 | +# +--------+------------+------------+------------+------------+------------+ +# +# NOTE: The above table was obtained by running the following commands: +# +# redis-benchmark -n 1000000 incr foo +# redis-cli object freq foo +# +# NOTE 2: The counter initial value is 5 in order to give new objects a chance +# to accumulate hits. +# +# The counter decay time is the time, in minutes, that must elapse in order +# for the key counter to be divided by two (or decremented if it has a value +# less <= 10). +# +# The default value for the lfu-decay-time is 1. A special value of 0 means to +# decay the counter every time it happens to be scanned. +# +# lfu-log-factor 10 +# lfu-decay-time 1 + +########################### ACTIVE DEFRAGMENTATION ####################### +# +# What is active defragmentation? +# ------------------------------- +# +# Active (online) defragmentation allows a Redis server to compact the +# spaces left between small allocations and deallocations of data in memory, +# thus allowing to reclaim back memory. +# +# Fragmentation is a natural process that happens with every allocator (but +# less so with Jemalloc, fortunately) and certain workloads. Normally a server +# restart is needed in order to lower the fragmentation, or at least to flush +# away all the data and create it again. However thanks to this feature +# implemented by Oran Agra for Redis 4.0 this process can happen at runtime +# in a "hot" way, while the server is running. +# +# Basically when the fragmentation is over a certain level (see the +# configuration options below) Redis will start to create new copies of the +# values in contiguous memory regions by exploiting certain specific Jemalloc +# features (in order to understand if an allocation is causing fragmentation +# and to allocate it in a better place), and at the same time, will release the +# old copies of the data. This process, repeated incrementally for all the keys +# will cause the fragmentation to drop back to normal values. +# +# Important things to understand: +# +# 1. This feature is disabled by default, and only works if you compiled Redis +# to use the copy of Jemalloc we ship with the source code of Redis. +# This is the default with Linux builds. +# +# 2. You never need to enable this feature if you don't have fragmentation +# issues. +# +# 3. Once you experience fragmentation, you can enable this feature when +# needed with the command "CONFIG SET activedefrag yes". +# +# The configuration parameters are able to fine tune the behavior of the +# defragmentation process. If you are not sure about what they mean it is +# a good idea to leave the defaults untouched. + +# Active defragmentation is disabled by default +# activedefrag no + +# Minimum amount of fragmentation waste to start active defrag +# active-defrag-ignore-bytes 100mb + +# Minimum percentage of fragmentation to start active defrag +# active-defrag-threshold-lower 10 + +# Maximum percentage of fragmentation at which we use maximum effort +# active-defrag-threshold-upper 100 + +# Minimal effort for defrag in CPU percentage, to be used when the lower +# threshold is reached +# active-defrag-cycle-min 1 + +# Maximal effort for defrag in CPU percentage, to be used when the upper +# threshold is reached +# active-defrag-cycle-max 25 + +# Maximum number of set/hash/zset/list fields that will be processed from +# the main dictionary scan +# active-defrag-max-scan-fields 1000 + +# Jemalloc background thread for purging will be enabled by default +jemalloc-bg-thread yes + +# It is possible to pin different threads and processes of Redis to specific +# CPUs in your system, in order to maximize the performances of the server. +# This is useful both in order to pin different Redis threads in different +# CPUs, but also in order to make sure that multiple Redis instances running +# in the same host will be pinned to different CPUs. +# +# Normally you can do this using the "taskset" command, however it is also +# possible to this via Redis configuration directly, both in Linux and FreeBSD. +# +# You can pin the server/IO threads, bio threads, aof rewrite child process, and +# the bgsave child process. The syntax to specify the cpu list is the same as +# the taskset command: +# +# Set redis server/io threads to cpu affinity 0,2,4,6: +# server_cpulist 0-7:2 +# +# Set bio threads to cpu affinity 1,3: +# bio_cpulist 1,3 +# +# Set aof rewrite child process to cpu affinity 8,9,10,11: +# aof_rewrite_cpulist 8-11 +# +# Set bgsave child process to cpu affinity 1,10,11 +# bgsave_cpulist 1,10-11 + +# In some cases redis will emit warnings and even refuse to start if it detects +# that the system is in bad state, it is possible to suppress these warnings +# by setting the following config which takes a space delimited list of warnings +# to suppress +# +# ignore-warnings ARM64-COW-BUG +aclfile /etc/redis/users.acl diff --git a/backend/baton/redis/users.acl b/backend/baton/redis/users.acl new file mode 100644 index 000000000..df76d25f5 --- /dev/null +++ b/backend/baton/redis/users.acl @@ -0,0 +1 @@ +user admin on +@all ~* >admin diff --git a/backend/baton/secret b/backend/baton/secret index 9a4adb562..4223f4655 160000 --- a/backend/baton/secret +++ b/backend/baton/secret @@ -1 +1 @@ -Subproject commit 9a4adb562b9e9d299f21d6fc8ca457078a8eaf60 +Subproject commit 4223f46551ffa30795cb8e7ea9353c5932939cf5 diff --git a/backend/baton/src/main/java/touch/baton/config/RedisConfig.java b/backend/baton/src/main/java/touch/baton/config/RedisConfig.java new file mode 100644 index 000000000..66124aaee --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/config/RedisConfig.java @@ -0,0 +1,37 @@ +package touch.baton.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; + +@Profile("!test") +@Configuration +@EnableRedisRepositories +public class RedisConfig { + + @Value("${spring.redis.host}") + private String host; + + @Value("${spring.redis.port}") + private int port; + + @Value("${spring.redis.username}") + private String username; + + @Value("${spring.redis.password}") + private String password; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port); + redisStandaloneConfiguration.setDatabase(0); + redisStandaloneConfiguration.setUsername(username); + redisStandaloneConfiguration.setPassword(password); + return new LettuceConnectionFactory(redisStandaloneConfiguration); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/controller/OauthCommandController.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/controller/OauthCommandController.java index 497888c99..896cd064f 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/command/controller/OauthCommandController.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/controller/OauthCommandController.java @@ -4,7 +4,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.boot.web.server.Cookie.SameSite; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CookieValue; @@ -22,14 +21,15 @@ import touch.baton.domain.oauth.command.OauthType; import touch.baton.domain.oauth.command.service.OauthCommandService; import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.domain.oauth.query.controller.resolver.AuthMemberPrincipal; import java.io.IOException; -import java.time.Duration; -import java.time.LocalDateTime; +import static org.springframework.boot.web.server.Cookie.SameSite.NONE; import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.HttpHeaders.SET_COOKIE; import static org.springframework.http.HttpStatus.FOUND; @RequiredArgsConstructor @@ -77,7 +77,7 @@ public ResponseEntity refreshJwt(@Nullable @CookieValue(required = false) final AuthorizationHeader authorizationHeader = new AuthorizationHeader(request.getHeader(AUTHORIZATION)); - final Tokens tokens = oauthCommandService.reissueAccessToken(authorizationHeader, refreshToken); + final Tokens tokens = oauthCommandService.reissueAccessToken(authorizationHeader, new Token(refreshToken)); setCookie(response, tokens.refreshToken()); @@ -90,11 +90,11 @@ private void setCookie(final HttpServletResponse response, final RefreshToken re final ResponseCookie responseCookie = ResponseCookie.from("refreshToken", refreshToken.getToken().getValue()) .httpOnly(true) .secure(true) - .maxAge(Duration.between(LocalDateTime.now(), refreshToken.getExpireDate().getValue()).toSeconds()) - .sameSite(SameSite.NONE.attributeValue()) + .maxAge(refreshToken.getTimeout()) + .sameSite(NONE.attributeValue()) .path("/") .build(); - response.addHeader("Set-Cookie", responseCookie.toString()); + response.addHeader(SET_COOKIE, responseCookie.toString()); } @PatchMapping("/logout") diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java index c973ffde4..c42d39934 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepository.java @@ -1,17 +1,7 @@ package touch.baton.domain.oauth.command.repository; -import org.springframework.data.jpa.repository.JpaRepository; -import touch.baton.domain.member.command.Member; +import org.springframework.data.repository.CrudRepository; import touch.baton.domain.oauth.command.token.RefreshToken; -import touch.baton.domain.oauth.command.token.Token; -import java.util.Optional; - -public interface RefreshTokenCommandRepository extends JpaRepository { - - Optional findByToken(final Token token); - - Optional findByMember(final Member member); - - void deleteByMember(final Member member); +public interface RefreshTokenCommandRepository extends CrudRepository { } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/OauthCommandService.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/OauthCommandService.java index 078649e85..68c20395c 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/OauthCommandService.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/OauthCommandService.java @@ -2,7 +2,6 @@ import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import touch.baton.domain.common.exception.ClientErrorCode; @@ -21,21 +20,13 @@ import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; -import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; -import touch.baton.domain.oauth.command.token.AccessToken; -import touch.baton.domain.oauth.command.token.ExpireDate; -import touch.baton.domain.oauth.command.token.RefreshToken; import touch.baton.domain.oauth.command.token.Token; import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.domain.technicaltag.command.SupporterTechnicalTags; import touch.baton.infra.auth.jwt.JwtDecoder; -import touch.baton.infra.auth.jwt.JwtEncoder; -import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Map; import java.util.Optional; -import java.util.UUID; @RequiredArgsConstructor @Transactional @@ -47,13 +38,9 @@ public class OauthCommandService { private final OauthMemberCommandRepository oauthMemberCommandRepository; private final OauthRunnerCommandRepository oauthRunnerCommandRepository; private final OauthSupporterCommandRepository oauthSupporterCommandRepository; - private final RefreshTokenCommandRepository refreshTokenCommandRepository; - private final JwtEncoder jwtEncoder; + private final TokenFacade tokenFacade; private final JwtDecoder jwtDecoder; - @Value("${refresh_token.expire_minutes}") - private int refreshTokenExpireMinutes; - public String readAuthCodeRedirect(final OauthType oauthType) { return authCodeRequestUrlProviderComposite.findRequestUrl(oauthType); } @@ -66,10 +53,10 @@ public Tokens login(final OauthType oauthType, final String code) { final Member savedMember = signUpMember(oauthInformation); saveNewRunner(savedMember); saveNewSupporter(savedMember); - return createTokens(oauthInformation.getSocialId(), savedMember); + return tokenFacade.createTokens(savedMember); } - return createTokens(oauthInformation.getSocialId(), maybeMember.get()); + return tokenFacade.createTokens(maybeMember.get()); } private Member signUpMember(final OauthInformation oauthInformation) { @@ -85,82 +72,34 @@ private Member signUpMember(final OauthInformation oauthInformation) { return oauthMemberCommandRepository.save(newMember); } - private Runner saveNewRunner(final Member member) { + private void saveNewRunner(final Member member) { final Runner newRunner = Runner.builder() .member(member) .build(); - return oauthRunnerCommandRepository.save(newRunner); + oauthRunnerCommandRepository.save(newRunner); } - private Supporter saveNewSupporter(final Member member) { + private void saveNewSupporter(final Member member) { final Supporter newSupporter = Supporter.builder() .reviewCount(new ReviewCount(0)) .member(member) .supporterTechnicalTags(new SupporterTechnicalTags(new ArrayList<>())) .build(); - return oauthSupporterCommandRepository.save(newSupporter); - } - - private Tokens createTokens(final SocialId socialId, final Member member) { - final AccessToken accessToken = createAccessToken(socialId); - - final String randomTokens = UUID.randomUUID().toString(); - final Token token = new Token(randomTokens); - final LocalDateTime expireDate = LocalDateTime.now().plusMinutes(refreshTokenExpireMinutes); - final RefreshToken refreshToken = RefreshToken.builder() - .member(member) - .token(token) - .expireDate(new ExpireDate(expireDate)) - .build(); - - final Optional maybeRefreshToken = refreshTokenCommandRepository.findByMember(member); - if (maybeRefreshToken.isPresent()) { - final RefreshToken findRefreshToken = maybeRefreshToken.get(); - findRefreshToken.updateToken(new Token(randomTokens), refreshTokenExpireMinutes); - return new Tokens(accessToken, findRefreshToken); - } - - refreshTokenCommandRepository.save(refreshToken); - return new Tokens(accessToken, refreshToken); + oauthSupporterCommandRepository.save(newSupporter); } - public Tokens reissueAccessToken(final AuthorizationHeader authHeader, final String refreshToken) { + public Tokens reissueAccessToken(final AuthorizationHeader authHeader, final Token refreshToken) { final Claims claims = jwtDecoder.parseExpiredAuthorizationHeader(authHeader); final SocialId socialId = new SocialId(claims.get("socialId", String.class)); final Member findMember = oauthMemberCommandRepository.findBySocialId(socialId) .orElseThrow(() -> new OauthRequestException(ClientErrorCode.JWT_CLAIM_SOCIAL_ID_IS_WRONG)); - final RefreshToken findRefreshToken = refreshTokenCommandRepository.findByToken(new Token(refreshToken)) - .orElseThrow(() -> new OauthRequestException(ClientErrorCode.REFRESH_TOKEN_IS_NOT_FOUND)); - - if (findRefreshToken.isNotOwner(findMember)) { - throw new OauthRequestException(ClientErrorCode.ACCESS_TOKEN_AND_REFRESH_TOKEN_HAVE_DIFFERENT_OWNER); - } - if (findRefreshToken.isExpired()) { - throw new OauthRequestException(ClientErrorCode.REFRESH_TOKEN_IS_ALREADY_EXPIRED); - } - - return reissueTokens(socialId, findRefreshToken); - } - - private Tokens reissueTokens(final SocialId socialId, final RefreshToken refreshToken) { - final AccessToken accessToken = createAccessToken(socialId); - - refreshToken.updateToken(new Token(UUID.randomUUID().toString()), refreshTokenExpireMinutes); - - return new Tokens(accessToken, refreshToken); - } - - private AccessToken createAccessToken(final SocialId socialId) { - final String jwtToken = jwtEncoder.jwtToken(Map.of( - "socialId", socialId.getValue()) - ); - return new AccessToken(jwtToken); + return tokenFacade.reissueAccessToken(findMember, refreshToken); } public void logout(final Member member) { - refreshTokenCommandRepository.deleteByMember(member); + tokenFacade.logout(member.getSocialId()); } } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/TokenFacade.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/TokenFacade.java new file mode 100644 index 000000000..73833adac --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/service/TokenFacade.java @@ -0,0 +1,75 @@ +package touch.baton.domain.oauth.command.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.common.exception.ClientErrorCode; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.SocialId; +import touch.baton.domain.oauth.command.exception.OauthRequestException; +import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; +import touch.baton.domain.oauth.command.token.AccessToken; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.domain.oauth.command.token.Tokens; +import touch.baton.infra.auth.jwt.JwtEncoder; + +import java.util.Map; +import java.util.UUID; + +@Transactional +@Component +public class TokenFacade { + + private final RefreshTokenCommandRepository refreshTokenCommandRepository; + private final JwtEncoder jwtEncoder; + + private Long refreshTokenExpireMinutes; + + public TokenFacade(final RefreshTokenCommandRepository refreshTokenCommandRepository, final JwtEncoder jwtEncoder, @Value("${spring.redis.refresh_token.expire_minutes}") final Long refreshTokenExpireMinutes) { + this.refreshTokenCommandRepository = refreshTokenCommandRepository; + this.jwtEncoder = jwtEncoder; + this.refreshTokenExpireMinutes = refreshTokenExpireMinutes; + } + + public Tokens reissueAccessToken(final Member member, final Token refreshToken) { + final RefreshToken findRefreshToken = refreshTokenCommandRepository.findById(member.getSocialId().getValue()) + .orElseThrow(() -> new OauthRequestException(ClientErrorCode.REFRESH_TOKEN_IS_NOT_FOUND)); + if (findRefreshToken.isNotOwner(refreshToken)) { + throw new OauthRequestException(ClientErrorCode.ACCESS_TOKEN_AND_REFRESH_TOKEN_HAVE_DIFFERENT_OWNER); + } + + return createTokens(member); + } + + public Tokens createTokens(final Member member) { + final AccessToken accessToken = createAccessToken(member.getSocialId()); + final RefreshToken refreshToken = createRefreshToken(member); + + return new Tokens(accessToken, refreshToken); + } + + private AccessToken createAccessToken(final SocialId socialId) { + final String jwtToken = jwtEncoder.jwtToken( + Map.of("socialId", socialId.getValue())); + return new AccessToken(jwtToken); + } + + private RefreshToken createRefreshToken(final Member member) { + final Token token = new Token(UUID.randomUUID().toString()); + final RefreshToken refreshToken = RefreshToken.builder() + .socialId(member.getSocialId().getValue()) + .member(member) + .token(token) + .timeout(refreshTokenExpireMinutes) + .build(); + + refreshTokenCommandRepository.save(refreshToken); + + return refreshToken; + } + + public void logout(final SocialId socialId) { + refreshTokenCommandRepository.deleteById(socialId.getValue()); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/ExpireDate.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/ExpireDate.java deleted file mode 100644 index 1efe615af..000000000 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/ExpireDate.java +++ /dev/null @@ -1,40 +0,0 @@ -package touch.baton.domain.oauth.command.token; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -import static lombok.AccessLevel.PROTECTED; - -@EqualsAndHashCode -@Getter -@NoArgsConstructor(access = PROTECTED) -@Embeddable -public class ExpireDate { - - @Column(name = "expire_date", nullable = false) - private LocalDateTime value; - - public ExpireDate(final LocalDateTime value) { - validateNotNull(value); - this.value = value; - } - - private void validateNotNull(final LocalDateTime value) { - if (value == null) { - throw new IllegalArgumentException("ExpireDate 의 value 는 null일 수 없습니다."); - } - } - - public void refreshExpireTokenDate(final int minutes) { - this.value = LocalDateTime.now().plusMinutes(minutes); - } - - public boolean isExpired() { - return LocalDateTime.now().isAfter(value); - } -} diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/RefreshToken.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/RefreshToken.java index ca5a9e96f..e291e4e66 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/RefreshToken.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/RefreshToken.java @@ -1,77 +1,56 @@ package touch.baton.domain.oauth.command.token; -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.NoArgsConstructor; -import touch.baton.domain.common.BaseEntity; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.TimeToLive; import touch.baton.domain.member.command.Member; import touch.baton.domain.oauth.command.token.exception.RefreshTokenDomainException; -import static jakarta.persistence.FetchType.LAZY; -import static jakarta.persistence.GenerationType.IDENTITY; -import static lombok.AccessLevel.PROTECTED; +import java.util.concurrent.TimeUnit; +@EqualsAndHashCode @Getter -@NoArgsConstructor(access = PROTECTED) -@Entity -public class RefreshToken extends BaseEntity { +@RedisHash(value = "token:refresh") +public class RefreshToken { @Id - @GeneratedValue(strategy = IDENTITY) - private Long id; + private String socialId; - @OneToOne(fetch = LAZY) - @JoinColumn(name = "member_id", foreignKey = @ForeignKey(name = "fk_refresh_token_to_member"), nullable = false) - private Member member; - - @Embedded private Token token; - @Embedded - private ExpireDate expireDate; + private Member member; - @Builder - private RefreshToken(final Member member, final Token token, final ExpireDate expireDate) { - this(null, member, token, expireDate); - } + @TimeToLive(unit = TimeUnit.MINUTES) + private Long timeout; - private RefreshToken(final Long id, final Member member, final Token token, final ExpireDate expireDate) { - validateNotNull(member, token, expireDate); - this.id = id; - this.member = member; + @Builder + public RefreshToken(final String socialId, final Token token, final Member member, final Long timeout) { + validateNotNull(socialId, token, member, timeout); + this.socialId = socialId; this.token = token; - this.expireDate = expireDate; + this.member = member; + this.timeout = timeout; } - private void validateNotNull(final Member member, final Token token, final ExpireDate expireDate) { - if (member == null) { - throw new RefreshTokenDomainException("RefreshToken 의 member 는 null 일 수 없습니다."); + private void validateNotNull(final String socialId, final Token token, final Member member, final Long timeout) { + if (socialId == null) { + throw new RefreshTokenDomainException("RefreshToken 의 socialId 는 null 일 수 없습니다."); } if (token == null) { throw new RefreshTokenDomainException("RefreshToken 의 token 은 null 일 수 없습니다."); } - if (expireDate == null) { - throw new RefreshTokenDomainException("RefreshToken 의 expireDate 는 null 일 수 없습니다."); + if (member == null) { + throw new RefreshTokenDomainException("RefreshToken 의 member 는 null 일 수 없습니다."); + } + if (timeout == null) { + throw new RefreshTokenDomainException("RefreshToken 의 timeout 은 null 일 수 없습니다."); } } - public void updateToken(final Token token, final int expiredMinutes) { - this.token = token; - expireDate.refreshExpireTokenDate(expiredMinutes); - } - - public boolean isNotOwner(final Member member) { - return !this.member.equals(member); - } - - public boolean isExpired() { - return expireDate.isExpired(); + public boolean isNotOwner(final Token target) { + return !token.equals(target); } } diff --git a/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Token.java b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Token.java index bbd692a5b..b10d5c630 100644 --- a/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Token.java +++ b/backend/baton/src/main/java/touch/baton/domain/oauth/command/token/Token.java @@ -1,21 +1,13 @@ package touch.baton.domain.oauth.command.token; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.NoArgsConstructor; import org.springframework.util.ObjectUtils; -import static lombok.AccessLevel.PROTECTED; - @EqualsAndHashCode @Getter -@NoArgsConstructor(access = PROTECTED) -@Embeddable public class Token { - @Column(name = "token", nullable = false, columnDefinition = "text") private String value; public Token(final String value) { @@ -29,3 +21,4 @@ private void validateNotNull(final String value) { } } } + diff --git a/backend/baton/src/main/resources/application.yml b/backend/baton/src/main/resources/application.yml index c99aff939..9b82089e2 100644 --- a/backend/baton/src/main/resources/application.yml +++ b/backend/baton/src/main/resources/application.yml @@ -16,6 +16,14 @@ spring: default-page-size: 10 one-indexed-parameters: true + redis: + host: ${REDIS_HOST} + port: ${REDIS_PORT} + username: ${REDIS_USERNAME} + password: ${REDIS_PASSWORD + refresh_token: + expire_minutes: ${REFRESH_TOKEN_MINUTES} + logging: config: classpath:logs/log4j2.xml level: @@ -40,6 +48,3 @@ jwt: secret_key: ${JWT_SECRET_KEY} issuer: ${JWT_ISSUER} expire_minutes: ${JWT_EXPIRE_MINUTES} - -refresh_token: - expire_minutes: ${REFRESH_TOKEN_MINUTES} diff --git a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java index a04fc622f..884b4ca20 100644 --- a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java +++ b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthAssuredSupport.java @@ -11,12 +11,10 @@ import touch.baton.domain.member.command.Member; import touch.baton.domain.oauth.command.OauthType; import touch.baton.domain.oauth.command.token.AccessToken; -import touch.baton.domain.oauth.command.token.ExpireDate; import touch.baton.domain.oauth.command.token.RefreshToken; import touch.baton.domain.oauth.command.token.Token; import touch.baton.domain.oauth.command.token.Tokens; -import java.time.LocalDateTime; import java.util.Map; import static org.assertj.core.api.SoftAssertions.assertSoftly; @@ -140,12 +138,11 @@ public OauthServerResponseBuilder(final ExtractableResponse response) public Tokens 액세스_토큰과_리프레시_토큰을_반환한다(final Member ethan) { final String accessToken = response.header(AUTHORIZATION); - - final LocalDateTime expireDate = LocalDateTime.now().plusDays(30); final RefreshToken refreshToken = RefreshToken.builder() + .socialId(ethan.getSocialId().getValue()) .member(ethan) .token(new Token(response.cookie("refreshToken"))) - .expireDate(new ExpireDate(expireDate)) + .timeout(30L) .build(); return new Tokens(new AccessToken(accessToken), refreshToken); diff --git a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java index 2a936f786..f90ae9d9b 100644 --- a/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/oauth/OauthRefreshTokenAssuredTest.java @@ -7,14 +7,10 @@ import touch.baton.domain.common.exception.ClientErrorCode; import touch.baton.domain.member.command.vo.SocialId; import touch.baton.domain.oauth.command.OauthType; -import touch.baton.domain.oauth.command.token.ExpireDate; -import touch.baton.domain.oauth.command.token.Token; import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.vo.ExpireDateFixture; import touch.baton.infra.auth.jwt.JwtEncoder; -import java.time.LocalDateTime; import java.util.Map; @SuppressWarnings("NonAsciiCharacters") @@ -195,7 +191,7 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { .기간_만료_액세스_토큰과_리프레시_토큰으로_리프레시_요청한다(만료된_액세스_토큰, 만료된_리프레시_토큰) .서버_응답() - .오류가_발생한다(ClientErrorCode.REFRESH_TOKEN_IS_ALREADY_EXPIRED); + .오류가_발생한다(ClientErrorCode.REFRESH_TOKEN_IS_NOT_FOUND); } private String 기간_만료_액세스_토큰을_생성한다(final Tokens 액세스_토큰과_리프레시_토큰) { @@ -206,11 +202,8 @@ class OauthRefreshTokenAssuredTest extends AssuredTestConfig { } private String 만료된_리프레시_토큰을_가져온다(final Tokens 액세스_토큰과_리프레시_토큰) { - final Token 토큰 = 액세스_토큰과_리프레시_토큰.refreshToken().getToken(); - final ExpireDate 기간_만료일 = ExpireDateFixture.expireDate(LocalDateTime.now().minusDays(14)); + refreshTokenRepository.expireRefreshToken(액세스_토큰과_리프레시_토큰.refreshToken()); - refreshTokenRepository.changeExpireDateByToken(토큰, 기간_만료일); - - return 토큰.getValue(); + return 액세스_토큰과_리프레시_토큰.refreshToken().getToken().getValue(); } } diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java index d19ddd643..e0dc4334b 100644 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestRefreshTokenRepository.java @@ -1,22 +1,25 @@ package touch.baton.assure.repository; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.transaction.annotation.Transactional; -import touch.baton.domain.oauth.command.token.ExpireDate; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; import touch.baton.domain.oauth.command.token.RefreshToken; -import touch.baton.domain.oauth.command.token.Token; -public interface TestRefreshTokenRepository extends JpaRepository { +import java.time.Duration; - @Modifying - @Transactional - @Query(""" - update RefreshToken rt - set rt.expireDate = :expireDate - where rt.token = :token - """) - void changeExpireDateByToken(@Param("token") final Token token, @Param("expireDate") final ExpireDate expireDate); +@Repository +public class TestRefreshTokenRepository { + + private final RedisTemplate redisTemplate; + + public TestRefreshTokenRepository(final RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + public void save(final RefreshToken refreshToken) { + redisTemplate.opsForHash().put(String.format("token:refresh:%s", refreshToken.getSocialId()), refreshToken.getToken().getValue(), Duration.ofSeconds(30)); + } + + public void expireRefreshToken(final RefreshToken refreshToken) { + redisTemplate.expire(String.format("token:refresh:%s", refreshToken.getSocialId()), Duration.ZERO); + } } diff --git a/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java b/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java index 417005ffa..44d5d5f77 100644 --- a/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; @@ -22,9 +23,11 @@ import touch.baton.assure.repository.TestTagQuerydslRepository; import touch.baton.config.converter.ConverterConfig; import touch.baton.config.infra.auth.MockBeanAuthTestConfig; +import touch.baton.config.infra.database.TestRedisContainerConfig; import touch.baton.config.infra.github.MockGithubBranchServiceConfig; @ActiveProfiles("test") +@ExtendWith(TestRedisContainerConfig.class) @Import({JpaConfig.class, QuerydslConfig.class, ConverterConfig.class, PageableTestConfig.class, MockBeanAuthTestConfig.class, MockGithubBranchServiceConfig.class, JwtTestManager.class}) @TestExecutionListeners(value = AssuredTestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) diff --git a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java deleted file mode 100644 index b1ce87bc0..000000000 --- a/backend/baton/src/test/java/touch/baton/config/infra/auth/oauth/MockRefreshTokenConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package touch.baton.config.infra.auth.oauth; - -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import touch.baton.domain.oauth.command.token.RefreshToken; -import touch.baton.infra.auth.jwt.JwtConfig; -import touch.baton.infra.auth.jwt.JwtEncoder; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@TestConfiguration -public class MockRefreshTokenConfig { - - @Bean - JwtEncoder expiredJwtDecoder() { - return new JwtEncoder(mockJwtConfig()); - } - - private JwtConfig mockJwtConfig() { - return new JwtConfig("test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key_test_secret_key", "test_issuer", -1); - } - - @Bean - RefreshToken refreshToken() { - final RefreshToken mock = mock(RefreshToken.class); - - /** - * 다야한 when 절 - */ - when(mock.getToken().getValue()).thenReturn("mock refresh token"); - - return mock; - } -} diff --git a/backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisConfig.java new file mode 100644 index 000000000..42356ff6e --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisConfig.java @@ -0,0 +1,37 @@ +package touch.baton.config.infra.database; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Profile("test") +@Configuration +public class TestRedisConfig { + + @Value("${spring.redis.host}") + private String host; + + @Value("${spring.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port); + return new LettuceConnectionFactory(redisStandaloneConfiguration); + } + + @Bean + public RedisTemplate redisTemplate() { + final RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } +} diff --git a/backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisContainerConfig.java b/backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisContainerConfig.java new file mode 100644 index 000000000..8dfa237e1 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/config/infra/database/TestRedisContainerConfig.java @@ -0,0 +1,24 @@ +package touch.baton.config.infra.database; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +public class TestRedisContainerConfig implements BeforeAllCallback { + + private static final String REDIS_IMAGE = "redis:7.0.8-alpine"; + private static final int REDIS_PORT = 6379; + + private GenericContainer redisContainer; + + @Override + public void beforeAll(ExtensionContext context) { + redisContainer = new GenericContainer(DockerImageName.parse(REDIS_IMAGE)) + .withExposedPorts(REDIS_PORT); + redisContainer.start(); + + System.setProperty("spring.redis.host", redisContainer.getHost()); + System.setProperty("spring.redis.port", redisContainer.getMappedPort(6379).toString()); + } +} diff --git a/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java b/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java index 9c733933f..0ef27355d 100644 --- a/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/oauth/github/GithubOauthApiTest.java @@ -6,14 +6,11 @@ import touch.baton.config.RestdocsConfig; import touch.baton.domain.member.command.Member; import touch.baton.domain.oauth.command.token.AccessToken; -import touch.baton.domain.oauth.command.token.ExpireDate; import touch.baton.domain.oauth.command.token.RefreshToken; import touch.baton.domain.oauth.command.token.Token; import touch.baton.domain.oauth.command.token.Tokens; -import java.time.LocalDateTime; - -import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.commons.codec.CharEncoding.UTF_8; import static org.mockito.BDDMockito.when; import static org.mockito.Mockito.mock; import static org.springframework.http.HttpHeaders.LOCATION; @@ -60,9 +57,10 @@ void github_redirect_auth_code() throws Exception { void github_login() throws Exception { // given, when final RefreshToken refreshToken = RefreshToken.builder() + .socialId("mock socialId") .member(mock(Member.class)) .token(new Token("mock refresh token")) - .expireDate(new ExpireDate(LocalDateTime.now().plusDays(30))) + .timeout(30L) .build(); final Tokens tokens = new Tokens(new AccessToken("Bearer Jwt"), refreshToken); diff --git a/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java b/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java index ce0f7a6ce..afdc275ae 100644 --- a/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/oauth/token/RefreshTokenApiTest.java @@ -6,18 +6,16 @@ import touch.baton.config.RestdocsConfig; import touch.baton.domain.oauth.command.AuthorizationHeader; import touch.baton.domain.oauth.command.token.AccessToken; -import touch.baton.domain.oauth.command.token.ExpireDate; import touch.baton.domain.oauth.command.token.RefreshToken; import touch.baton.domain.oauth.command.token.Token; import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.fixture.domain.MemberFixture; import java.time.Duration; -import java.time.LocalDateTime; -import static org.apache.http.HttpHeaders.AUTHORIZATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName; import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies; import static org.springframework.restdocs.cookies.CookieDocumentation.responseCookies; @@ -34,14 +32,15 @@ class RefreshTokenApiTest extends RestdocsConfig { void refresh() throws Exception { // given, when final RefreshToken refreshToken = RefreshToken.builder() + .socialId("mock socialId") .token(new Token("refresh-token")) .member(MemberFixture.createEthan()) - .expireDate(new ExpireDate(LocalDateTime.now().plusDays(30))) + .timeout(30L) .build(); final Tokens tokens = new Tokens(new AccessToken("renew access token"), refreshToken); final Cookie cookie = createCookie(); - given(oauthCommandService.reissueAccessToken(any(AuthorizationHeader.class), any(String.class))).willReturn(tokens); + given(oauthCommandService.reissueAccessToken(any(AuthorizationHeader.class), any(Token.class))).willReturn(tokens); // then mockMvc.perform(post("/api/v1/oauth/refresh") diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepositoryTest.java deleted file mode 100644 index b66d7e744..000000000 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/command/repository/RefreshTokenCommandRepositoryTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package touch.baton.domain.oauth.command.repository; - -import jakarta.persistence.EntityManager; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import touch.baton.config.RepositoryTestConfig; -import touch.baton.domain.member.command.Member; -import touch.baton.domain.oauth.command.token.RefreshToken; -import touch.baton.domain.oauth.command.token.Token; -import touch.baton.fixture.domain.MemberFixture; -import touch.baton.fixture.domain.RefreshTokenFixture; - -import java.time.LocalDateTime; -import java.util.Optional; - -import static java.time.LocalDateTime.now; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static touch.baton.fixture.vo.ExpireDateFixture.expireDate; -import static touch.baton.fixture.vo.TokenFixture.token; -import static touch.baton.util.TestDateFormatUtil.createExpireDate; - -class RefreshTokenCommandRepositoryTest extends RepositoryTestConfig { - - @Autowired - private RefreshTokenCommandRepository refreshTokenCommandRepository; - - @Autowired - private EntityManager em; - - @DisplayName("리프레시 토큰을 토큰으로 찾을 수 있다.") - @Test - void findByToken() { - // given - final Member ethan = persistMember(MemberFixture.createEthan()); - final Member ditoo = persistMember(MemberFixture.createDitoo()); - - final LocalDateTime expireDate = createExpireDate(now().plusDays(30)); - - final Token ethanToken = token("ethan RefreshToken"); - final RefreshToken expected = RefreshTokenFixture.create(ethan, ethanToken, expireDate(expireDate)); - final Token ditooToken = token("ditoo RefreshToken"); - final RefreshToken otherToken = RefreshTokenFixture.create(ditoo, ditooToken, expireDate(expireDate)); - em.persist(expected); - em.persist(otherToken); - - em.flush(); - em.clear(); - - // when - final Optional actual = refreshTokenCommandRepository.findByToken(ethanToken); - - // then - assertSoftly(softly -> { - softly.assertThat(actual).isPresent(); - softly.assertThat(actual.get().getToken()).isEqualTo(expected.getToken()); - softly.assertThat(actual.get().getExpireDate()).isEqualTo(expected.getExpireDate()); - }); - } - - @DisplayName("리프레시 토큰을 사용자로 찾을 수 있다.") - @Test - void findByMember() { - // given - final Member owner = persistMember(MemberFixture.createEthan()); - final Member notOwner = persistMember(MemberFixture.createDitoo()); - - final LocalDateTime expireDate = createExpireDate(now().plusDays(30)); - - final RefreshToken expected = RefreshTokenFixture.create(owner, token("ethan RefreshToken"), expireDate(expireDate)); - final RefreshToken differentRefreshToken = RefreshTokenFixture.create(notOwner, token("ditoo RefreshToken"), expireDate(expireDate)); - em.persist(expected); - em.persist(differentRefreshToken); - - em.flush(); - em.clear(); - - // when - final Optional actual = refreshTokenCommandRepository.findByMember(owner); - - // then - assertSoftly(softly -> { - softly.assertThat(actual).isPresent(); - softly.assertThat(actual.get().getToken()).isEqualTo(expected.getToken()); - softly.assertThat(actual.get().getExpireDate()).isEqualTo(expected.getExpireDate()); - }); - } - - @DisplayName("사용자를 이용해 리프레시 토큰을 삭제할 수 있다.") - @Test - void logout() { - // given - final Member owner = persistMember(MemberFixture.createEthan()); - - final LocalDateTime expireDate = createExpireDate(now().plusDays(30)); - - final RefreshToken expected = RefreshTokenFixture.create(owner, token("ethan RefreshToken"), expireDate(expireDate)); - em.persist(expected); - - em.flush(); - em.clear(); - - // when - refreshTokenCommandRepository.deleteByMember(owner); - - // then - assertThat(refreshTokenCommandRepository.findByMember(owner)).isNotPresent(); - } -} diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java index 7d5ebf654..304a060bd 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceDeleteTest.java @@ -1,7 +1,6 @@ package touch.baton.domain.oauth.command.service; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -12,10 +11,8 @@ import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; -import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; import touch.baton.fixture.domain.MemberFixture; import touch.baton.infra.auth.jwt.JwtDecoder; -import touch.baton.infra.auth.jwt.JwtEncoder; import static org.mockito.BDDMockito.verify; import static org.mockito.Mockito.only; @@ -41,23 +38,16 @@ class OauthCommandServiceDeleteTest { private OauthSupporterCommandRepository oauthSupporterCommandRepository; @Mock - private RefreshTokenCommandRepository refreshTokenCommandRepository; - - @Mock - private JwtEncoder jwtEncoder; - - @Mock - private JwtEncoder expiredJwtEncoder; + private TokenFacade tokenFacade; @Mock private JwtDecoder jwtDecoder; @BeforeEach void setUp() { - oauthCommandService = new OauthCommandService(authCodeRequestUrlProviderComposite, oauthInformationClientComposite, oauthMemberCommandRepository, oauthRunnerCommandRepository, oauthSupporterCommandRepository, refreshTokenCommandRepository, jwtEncoder, jwtDecoder); + oauthCommandService = new OauthCommandService(authCodeRequestUrlProviderComposite, oauthInformationClientComposite, oauthMemberCommandRepository, oauthRunnerCommandRepository, oauthSupporterCommandRepository, tokenFacade, jwtDecoder); } - @DisplayName("Member 로 RefreshToken 을 삭제할 수 있다.") @Test void success_logout() { // given @@ -67,6 +57,6 @@ void success_logout() { oauthCommandService.logout(ethan); // then - verify(refreshTokenCommandRepository, only()).deleteByMember(ethan); + verify(tokenFacade, only()).logout(ethan.getSocialId()); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceUpdateTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceUpdateTest.java index cdcb99b34..790fba404 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceUpdateTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/OauthCommandServiceUpdateTest.java @@ -15,17 +15,16 @@ import touch.baton.domain.oauth.command.repository.OauthMemberCommandRepository; import touch.baton.domain.oauth.command.repository.OauthRunnerCommandRepository; import touch.baton.domain.oauth.command.repository.OauthSupporterCommandRepository; -import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; -import touch.baton.domain.oauth.command.token.ExpireDate; import touch.baton.domain.oauth.command.token.RefreshToken; import touch.baton.domain.oauth.command.token.Token; import touch.baton.domain.oauth.command.token.Tokens; import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RefreshTokenFixture; +import touch.baton.fixture.domain.TokensFixture; import touch.baton.infra.auth.jwt.JwtConfig; import touch.baton.infra.auth.jwt.JwtDecoder; import touch.baton.infra.auth.jwt.JwtEncoder; -import java.time.LocalDateTime; import java.util.Map; import java.util.Optional; @@ -33,7 +32,9 @@ import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static touch.baton.fixture.vo.AccessTokenFixture.createAccessToken; import static touch.baton.fixture.vo.AuthorizationHeaderFixture.bearerAuthorizationHeader; +import static touch.baton.fixture.vo.TokenFixture.token; @ExtendWith(MockitoExtension.class) class OauthCommandServiceUpdateTest { @@ -56,7 +57,7 @@ class OauthCommandServiceUpdateTest { private OauthSupporterCommandRepository oauthSupporterCommandRepository; @Mock - private RefreshTokenCommandRepository refreshTokenCommandRepository; + private TokenFacade tokenFacade; private JwtEncoder jwtEncoder; @@ -70,7 +71,7 @@ void setUp() { jwtDecoder = new JwtDecoder(normalJwtConfig); jwtEncoder = new JwtEncoder(normalJwtConfig); - oauthCommandService = new OauthCommandService(authCodeRequestUrlProviderComposite, oauthInformationClientComposite, oauthMemberCommandRepository, oauthRunnerCommandRepository, oauthSupporterCommandRepository, refreshTokenCommandRepository, jwtEncoder, jwtDecoder); + oauthCommandService = new OauthCommandService(authCodeRequestUrlProviderComposite, oauthInformationClientComposite, oauthMemberCommandRepository, oauthRunnerCommandRepository, oauthSupporterCommandRepository, tokenFacade, jwtDecoder); final JwtConfig expiredJwtConfig = new JwtConfig("secret-key-secret-key-secret-key-secret-key-secret-key-secret-key", "test-issuer", -1); expiredJwtEncoder = new JwtEncoder(expiredJwtConfig); @@ -82,25 +83,24 @@ void success_reissueAccessToken() { // given final Member tokenOwner = MemberFixture.createEthan(); final AuthorizationHeader expiredAuthorizationHeader = bearerAuthorizationHeader(expiredJwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); - final String refreshTokenValue = "ethan-refresh-token"; - final RefreshToken beforeRefreshToken = RefreshToken.builder() - .member(tokenOwner) - .token(new Token(refreshTokenValue)) - .expireDate(new ExpireDate(LocalDateTime.now().plusDays(30))) - .build(); + final String originRefreshTokenValue = "ethan-refresh-token"; + + final String reissuedRefreshTokenValue = "ethan-reissued-refresh-token"; + final RefreshToken reissuedAccessToken = RefreshTokenFixture.create(tokenOwner.getSocialId().getValue(), reissuedRefreshTokenValue, tokenOwner); - given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); - given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); + final SocialId tokenOwnerSocialId = new SocialId(tokenOwner.getSocialId().getValue()); + given(oauthMemberCommandRepository.findBySocialId(eq(tokenOwnerSocialId))).willReturn(Optional.of(tokenOwner)); + given(tokenFacade.reissueAccessToken(tokenOwner, token(originRefreshTokenValue))).willReturn(TokensFixture.create(createAccessToken(), reissuedAccessToken)); // when - final Tokens tokens = oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue); + final Tokens tokens = oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, new Token(originRefreshTokenValue)); // then assertSoftly(softly -> { softly.assertThat(tokens.accessToken()).isNotNull(); softly.assertThat(tokens.accessToken().getValue()).isNotEqualTo(expiredAuthorizationHeader.parseBearerAccessToken()); softly.assertThat(tokens.refreshToken()).isNotNull(); - softly.assertThat(tokens.refreshToken().getToken().getValue()).isNotEqualTo(refreshTokenValue); + softly.assertThat(tokens.refreshToken().getToken().getValue()).isNotEqualTo(originRefreshTokenValue); }); } @@ -109,16 +109,11 @@ void success_reissueAccessToken() { void fail_reissueAccessToken_when_jwt_is_not_expired() { // given final Member tokenOwner = MemberFixture.createEthan(); - final AuthorizationHeader normalJwtToken = bearerAuthorizationHeader(jwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); final String refreshTokenValue = "ethan-refresh-token"; - final RefreshToken beforeRefreshToken = RefreshToken.builder() - .member(tokenOwner) - .token(new Token(refreshTokenValue)) - .expireDate(new ExpireDate(LocalDateTime.now().plusDays(30))) - .build(); + final AuthorizationHeader validAuthorizationHeader = bearerAuthorizationHeader(jwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); // when, then - assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(normalJwtToken, refreshTokenValue)).isInstanceOf(OauthRequestException.class); + assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(validAuthorizationHeader, new Token(refreshTokenValue))).isInstanceOf(OauthRequestException.class); } @DisplayName("없는 socialId 가 재발급 요청하면 오류가 발생한다.") @@ -126,68 +121,12 @@ void fail_reissueAccessToken_when_jwt_is_not_expired() { void fail_reissueAccessToken_when_socialId_not_exists() { // given final Member tokenOwner = MemberFixture.createEthan(); - final AuthorizationHeader expiredAuthorizationHeader = bearerAuthorizationHeader(expiredJwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); - final String refreshTokenValue = "ethan-refresh-token"; - - given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.empty()); - - // when, then - assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); - } - - @DisplayName("refreshToken 이 없으면 재발급 요청하면 오류가 발생한다.") - @Test - void fail_reissueAccessToken_when_refreshToken_not_exists() { - // given - final Member tokenOwner = MemberFixture.createEthan(); - final AuthorizationHeader expiredAuthorizationHeader = bearerAuthorizationHeader(expiredJwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); - final String refreshTokenValue = "ethan-refresh-token"; - - given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); - given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.empty()); - - // when, then - assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); - } - - @DisplayName("주인이 아닌 accessToken 으로 재발급 요청하면 오류가 발생한다.") - @Test - void fail_reissueAccessToken_when_not_owner_of_accessToken() { - // given - final Member tokenOwner = MemberFixture.createEthan(); - final AuthorizationHeader expiredAuthorizationHeader = bearerAuthorizationHeader(expiredJwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); final String refreshTokenValue = "ethan-refresh-token"; - final RefreshToken beforeRefreshToken = RefreshToken.builder() - .member(tokenOwner) - .token(new Token(refreshTokenValue)) - .expireDate(new ExpireDate(LocalDateTime.now().plusDays(30))) - .build(); - - final Member notTokenOwner = MemberFixture.createHyena(); - given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(notTokenOwner)); - given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); - - // when, then - assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); - } - - @DisplayName("만료된 refreshToken 으로 재발급 요청하면 오류가 발생한다.") - @Test - void fail_reissueAccessToken_when_refreshToken_is_expired() { - // given - final Member tokenOwner = MemberFixture.createEthan(); final AuthorizationHeader expiredAuthorizationHeader = bearerAuthorizationHeader(expiredJwtEncoder.jwtToken(Map.of("socialId", tokenOwner.getSocialId().getValue()))); - final String refreshTokenValue = "ethan-refresh-token"; - final RefreshToken beforeRefreshToken = RefreshToken.builder() - .member(tokenOwner) - .token(new Token(refreshTokenValue)) - .expireDate(new ExpireDate(LocalDateTime.now().minusDays(14))) - .build(); - given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.of(tokenOwner)); - given(refreshTokenCommandRepository.findByToken(eq(new Token(refreshTokenValue)))).willReturn(Optional.of(beforeRefreshToken)); + given(oauthMemberCommandRepository.findBySocialId(eq(new SocialId(tokenOwner.getSocialId().getValue())))).willReturn(Optional.empty()); // when, then - assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, refreshTokenValue)).isInstanceOf(OauthRequestException.class); + assertThatThrownBy(() -> oauthCommandService.reissueAccessToken(expiredAuthorizationHeader, new Token(refreshTokenValue))).isInstanceOf(OauthRequestException.class); } } diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeDeleteTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeDeleteTest.java new file mode 100644 index 000000000..0a9efe5a7 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeDeleteTest.java @@ -0,0 +1,48 @@ +package touch.baton.domain.oauth.command.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.infra.auth.jwt.JwtConfig; +import touch.baton.infra.auth.jwt.JwtEncoder; + +import static org.mockito.BDDMockito.only; +import static org.mockito.BDDMockito.verify; + +@ExtendWith(MockitoExtension.class) +class TokenFacadeDeleteTest { + + private TokenFacade tokenFacade; + + @Mock + private RefreshTokenCommandRepository refreshTokenCommandRepository; + + @Mock + private JwtEncoder jwtEncoder; + + @BeforeEach + void setUp() { + final JwtConfig normalJwtConfig = new JwtConfig("secret-key-secret-key-secret-key-secret-key-secret-key-secret-key", "test-issuer", 30); + jwtEncoder = new JwtEncoder(normalJwtConfig); + tokenFacade = new TokenFacade(refreshTokenCommandRepository, jwtEncoder, 30L); + } + + @DisplayName("만료되지 않은 Refreshtoken을 가지고 재발급을 요청하면 성공한다.") + @Test + void success() { + // given + final Member tokenOwner = MemberFixture.createEthan(); + + // when + tokenFacade.logout(tokenOwner.getSocialId()); + + // then + verify(refreshTokenCommandRepository, only()).deleteById(tokenOwner.getSocialId().getValue()); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeUpdateTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeUpdateTest.java new file mode 100644 index 000000000..25ba3bbff --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/service/TokenFacadeUpdateTest.java @@ -0,0 +1,100 @@ +package touch.baton.domain.oauth.command.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.oauth.command.exception.OauthRequestException; +import touch.baton.domain.oauth.command.repository.RefreshTokenCommandRepository; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Token; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.RefreshTokenFixture; +import touch.baton.infra.auth.jwt.JwtConfig; +import touch.baton.infra.auth.jwt.JwtEncoder; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.verify; + +@ExtendWith(MockitoExtension.class) +class TokenFacadeUpdateTest { + + private TokenFacade tokenFacade; + + @Mock + private RefreshTokenCommandRepository refreshTokenCommandRepository; + + @Mock + private JwtEncoder jwtEncoder; + + @BeforeEach + void setUp() { + final JwtConfig normalJwtConfig = new JwtConfig("secret-key-secret-key-secret-key-secret-key-secret-key-secret-key", "test-issuer", 30); + jwtEncoder = new JwtEncoder(normalJwtConfig); + tokenFacade = new TokenFacade(refreshTokenCommandRepository, jwtEncoder, 30L); + } + + @DisplayName("만료되지 않은 Refreshtoken을 가지고 재발급을 요청하면 성공한다.") + @Test + void success_reissueAccessToken() { + // given + final Member tokenOwner = MemberFixture.createEthan(); + final String refreshTokenValue = "ethan-refresh-token"; + + final RefreshToken ownerRefreshToken = RefreshTokenFixture.ethanRefreshToken(); + given(refreshTokenCommandRepository.findById(tokenOwner.getSocialId().getValue())).willReturn(Optional.of(ownerRefreshToken)); + + // when + // then + assertThatCode(() -> tokenFacade.reissueAccessToken(tokenOwner, new Token(refreshTokenValue))).doesNotThrowAnyException(); + } + + @DisplayName("주인이 아닌 accessToken 으로 재발급 요청하면 오류가 발생한다.") + @Test + void fail_reissueAccessToken_when_not_owner_of_accessToken() { + // given + final Member notTokenOwner = MemberFixture.createHyena(); + final RefreshToken notOwnerRefreshToken = RefreshTokenFixture.hyenaRefreshToken(); + + given(refreshTokenCommandRepository.findById(eq(notTokenOwner.getSocialId().getValue()))).willReturn(Optional.of(notOwnerRefreshToken)); + + // when, then + final String ownerRefreshTokenValue = "ethan-refresh-token"; + assertThatThrownBy(() -> tokenFacade.reissueAccessToken(notTokenOwner, new Token(ownerRefreshTokenValue))).isInstanceOf(OauthRequestException.class); + } + + @DisplayName("만료된 refreshToken 으로 재발급 요청하면 오류가 발생한다.") + @Test + void fail_reissueAccessToken_when_refreshToken_is_expired() { + // given + final Member tokenOwner = MemberFixture.createEthan(); + final String refreshTokenValue = "ethan-refresh-token"; + + given(refreshTokenCommandRepository.findById(eq(tokenOwner.getSocialId().getValue()))).willReturn(Optional.empty()); + + // when, then + assertThatThrownBy(() -> tokenFacade.reissueAccessToken(tokenOwner, new Token(refreshTokenValue))).isInstanceOf(OauthRequestException.class); + } + + @DisplayName("createTokens으로 AccessToken과 RefreshToken을 생성한다.") + @Test + void success_createTokens() { + // given + final Member tokenOwner = MemberFixture.createEthan(); + + // when + tokenFacade.createTokens(tokenOwner); + + // then + verify(refreshTokenCommandRepository).save(any(RefreshToken.class)); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/oauth/command/token/RefreshTokenTest.java b/backend/baton/src/test/java/touch/baton/domain/oauth/command/token/RefreshTokenTest.java index 3ff96740e..630150a21 100644 --- a/backend/baton/src/test/java/touch/baton/domain/oauth/command/token/RefreshTokenTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/oauth/command/token/RefreshTokenTest.java @@ -12,13 +12,10 @@ import touch.baton.domain.member.command.vo.SocialId; import touch.baton.domain.oauth.command.token.exception.RefreshTokenDomainException; -import java.time.LocalDateTime; - -import static java.time.LocalDateTime.now; -import static java.time.temporal.ChronoUnit.MINUTES; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; -import static touch.baton.domain.oauth.command.token.RefreshToken.builder; class RefreshTokenTest { @@ -38,20 +35,34 @@ class Create { @DisplayName("성공한다.") @Test void success() { - assertThatCode(() -> builder() + assertThatCode(() -> RefreshToken.builder() + .socialId(owner.getSocialId().getValue()) .member(owner) .token(new Token("refresh-token")) - .expireDate(new ExpireDate(now().plusDays(30))) + .timeout(30L) .build()); } + @DisplayName("SocialId가 null 이면 실패한다.") + @Test + void fail_if_socialId_is_null() { + assertThatThrownBy(() -> RefreshToken.builder() + .socialId(null) + .member(owner) + .token(new Token("refresh-token")) + .timeout(30L) + .build() + ).isInstanceOf(RefreshTokenDomainException.class); + } + @DisplayName("사용자가 null 이면 실패한다.") @Test void fail_if_member_is_null() { assertThatThrownBy(() -> RefreshToken.builder() + .socialId(owner.getSocialId().getValue()) .member(null) .token(new Token("refresh-token")) - .expireDate(new ExpireDate(now().plusDays(30))) + .timeout(30L) .build() ).isInstanceOf(RefreshTokenDomainException.class); } @@ -60,9 +71,10 @@ void fail_if_member_is_null() { @Test void fail_if_token_is_null() { assertThatThrownBy(() -> RefreshToken.builder() + .socialId(owner.getSocialId().getValue()) .member(owner) .token(null) - .expireDate(new ExpireDate(now().plusDays(30))) + .timeout(30L) .build() ).isInstanceOf(RefreshTokenDomainException.class); } @@ -71,87 +83,32 @@ void fail_if_token_is_null() { @Test void fail_if_expireDate_is_null() { assertThatThrownBy(() -> RefreshToken.builder() + .socialId(owner.getSocialId().getValue()) .member(owner) .token(new Token("refresh-token")) - .expireDate(null) + .timeout(null) .build() ).isInstanceOf(RefreshTokenDomainException.class); } } - @DisplayName("토큰을 업데이트하면 토큰의 내용과 마감기한이 바뀐다.") - @Test - void updateToken() { - // given - final LocalDateTime currentTime = now(); - final ExpireDate expectedExpireDate = new ExpireDate(currentTime); - final RefreshToken refreshToken = builder() - .member(owner) - .token(new Token("refresh-token")) - .expireDate(expectedExpireDate) - .build(); - - // when - final Token updateToken = new Token("update-token"); - refreshToken.updateToken(updateToken, 30); - - // then - assertAll( - () -> assertThat(refreshToken.getToken()).isEqualTo(updateToken), - () -> assertThat(refreshToken.getExpireDate().getValue().truncatedTo(MINUTES)) - .isEqualTo((currentTime.plusMinutes(30)).truncatedTo(MINUTES))); - } - @DisplayName("토큰의 주인을 확인할 수 있다.") @Test void isNotOwner() { // given - final LocalDateTime currentTime = now(); - final ExpireDate expectedExpireDate = new ExpireDate(currentTime); - final RefreshToken refreshToken = builder() + final RefreshToken refreshToken = RefreshToken.builder() + .socialId(owner.getSocialId().getValue()) .member(owner) - .token(new Token("refresh-token")) - .expireDate(expectedExpireDate) + .token(new Token("owner-refresh-token")) + .timeout(30L) .build(); - final Member notOwner = Member.builder() - .memberName(new MemberName("Not Owner")) - .socialId(new SocialId("notOwnerSocialId")) - .oauthId(new OauthId("notOwnerOauthId")) - .githubUrl(new GithubUrl("github.com/notOwner")) - .company(new Company("우아한테크코스")) - .imageUrl(new ImageUrl("김석호")) - .build(); + final Token notOwnerRefreshToken = new Token("not-owner-refresh-token"); // when, then assertAll( - () -> assertThat(refreshToken.isNotOwner(owner)).isFalse(), - () -> assertThat(refreshToken.isNotOwner(notOwner)).isTrue() + () -> assertThat(refreshToken.isNotOwner(refreshToken.getToken())).isFalse(), + () -> assertThat(refreshToken.isNotOwner(notOwnerRefreshToken)).isTrue() ); } - - @DisplayName("만료되었는지 확인한다.") - @Test - void isExpired() { - // given - final LocalDateTime currentTime = now().minusDays(20); - final ExpireDate expectedExpireDate = new ExpireDate(currentTime); - final RefreshToken refreshToken = builder() - .member(owner) - .token(new Token("refresh-token")) - .expireDate(expectedExpireDate) - .build(); - - final Member notOwner = Member.builder() - .memberName(new MemberName("Not Owner")) - .socialId(new SocialId("notOwnerSocialId")) - .oauthId(new OauthId("notOwnerOauthId")) - .githubUrl(new GithubUrl("github.com/notOwner")) - .company(new Company("우아한테크코스")) - .imageUrl(new ImageUrl("김석호")) - .build(); - - // when, then - assertThat(refreshToken.isExpired()).isTrue(); - } } diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java index b6bedd1b9..94256c538 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/RefreshTokenFixture.java @@ -1,20 +1,36 @@ package touch.baton.fixture.domain; import touch.baton.domain.member.command.Member; -import touch.baton.domain.oauth.command.token.ExpireDate; import touch.baton.domain.oauth.command.token.RefreshToken; import touch.baton.domain.oauth.command.token.Token; +import static touch.baton.fixture.vo.TokenFixture.token; + public abstract class RefreshTokenFixture { private RefreshTokenFixture() { } - public static RefreshToken create(final Member member, final Token refreshToken, final ExpireDate expireDate) { + public static RefreshToken create(final String socialId, final Token token, final Member member, final Long timeout) { return RefreshToken.builder() + .socialId(socialId) + .token(token) .member(member) - .token(refreshToken) - .expireDate(expireDate) + .timeout(timeout) .build(); } + + public static RefreshToken create(final String socialId, final String token, final Member member) { + return create(socialId, token(token), member, 30L); + } + + public static RefreshToken ethanRefreshToken() { + final Member ethan = MemberFixture.createEthan(); + return create(ethan.getSocialId().getValue(), token("ethan-refresh-token"), ethan, 30L); + } + + public static RefreshToken hyenaRefreshToken() { + final Member hyena = MemberFixture.createHyena(); + return create(hyena.getSocialId().getValue(), token("hyena-refresh-token"), hyena, 30L); + } } diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/TokensFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/TokensFixture.java new file mode 100644 index 000000000..0d66b94d3 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/TokensFixture.java @@ -0,0 +1,15 @@ +package touch.baton.fixture.domain; + +import touch.baton.domain.oauth.command.token.AccessToken; +import touch.baton.domain.oauth.command.token.RefreshToken; +import touch.baton.domain.oauth.command.token.Tokens; + +public abstract class TokensFixture { + + private TokensFixture() { + } + + public static Tokens create(final AccessToken accessToken, final RefreshToken refreshToken) { + return new Tokens(accessToken, refreshToken); + } +} diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/AccessTokenFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/AccessTokenFixture.java new file mode 100644 index 000000000..2f9293935 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/AccessTokenFixture.java @@ -0,0 +1,17 @@ +package touch.baton.fixture.vo; + +import touch.baton.domain.oauth.command.token.AccessToken; + +public abstract class AccessTokenFixture { + + private AccessTokenFixture() { + } + + public static AccessToken accessToken(final String value) { + return new AccessToken(value); + } + + public static AccessToken createAccessToken() { + return accessToken("access-token"); + } +} diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java index 9a5c7e0a9..e0708cff8 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/vo/AuthorizationHeaderFixture.java @@ -2,7 +2,7 @@ import touch.baton.domain.oauth.command.AuthorizationHeader; -public class AuthorizationHeaderFixture { +public abstract class AuthorizationHeaderFixture { private static final String BEARER = "Bearer "; diff --git a/backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java b/backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java deleted file mode 100644 index 98de3a810..000000000 --- a/backend/baton/src/test/java/touch/baton/fixture/vo/ExpireDateFixture.java +++ /dev/null @@ -1,15 +0,0 @@ -package touch.baton.fixture.vo; - -import touch.baton.domain.oauth.command.token.ExpireDate; - -import java.time.LocalDateTime; - -public abstract class ExpireDateFixture { - - private ExpireDateFixture() { - } - - public static ExpireDate expireDate(final LocalDateTime expireDate) { - return new ExpireDate(expireDate); - } -} diff --git a/backend/baton/src/test/resources/application.yml b/backend/baton/src/test/resources/application.yml index 311a5a907..162169ae2 100644 --- a/backend/baton/src/test/resources/application.yml +++ b/backend/baton/src/test/resources/application.yml @@ -15,14 +15,20 @@ spring: default-page-size: 10 one-indexed-parameters: true + redis: + host: localhost + port: 6379 + username: default + password: test + refresh_token: + expire_minutes: 1 + logging: config: classpath:logs/log4j2.xml level: org.hibernate.SQL: debug org.hibernate.orm.jdbc.bind: trace -refresh_token: - expire_minutes: 30 oauth: github: From 7c5c38a8161ab0a8d4f43dc238158c2d0272addd Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 17 Feb 2024 19:48:39 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20API=EC=97=90=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20?= =?UTF-8?q?=EC=B4=9D=20=EA=B0=9C=EC=88=98=20=ED=8F=AC=ED=95=A8=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20=EB=B0=98=ED=99=98=20(#711)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 글 조회시 전체 개수가 같이 반환되도록 구현 --- backend/baton/secret | 2 +- .../domain/common/response/PageResponse.java | 26 ++++++++- .../touch/baton/domain/common/vo/Page.java | 6 ++ .../repository/RunnerPostPageRepository.java | 14 ++++- .../query/service/RunnerPostQueryService.java | 11 ++-- .../query/page/RunnerPostPageAssuredTest.java | 18 ++++-- ...nnerPostReadOfSupporterByGuestApiTest.java | 3 +- .../read/RunnerPostReadSearchApiTest.java | 5 +- ...unnerPostReadWithLoginedRunnerApiTest.java | 3 +- ...erPostReadWithLoginedSupporterApiTest.java | 3 +- .../RunnerPostPageRepositoryTest.java | 51 ++++++++++------- .../service/RunnerPostQueryServiceTest.java | 57 ++++++++++++------- 12 files changed, 136 insertions(+), 63 deletions(-) create mode 100644 backend/baton/src/main/java/touch/baton/domain/common/vo/Page.java diff --git a/backend/baton/secret b/backend/baton/secret index 4223f4655..9a4adb562 160000 --- a/backend/baton/secret +++ b/backend/baton/secret @@ -1 +1 @@ -Subproject commit 4223f46551ffa30795cb8e7ea9353c5932939cf5 +Subproject commit 9a4adb562b9e9d299f21d6fc8ca457078a8eaf60 diff --git a/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java b/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java index 74df1330c..924de8334 100644 --- a/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java +++ b/backend/baton/src/main/java/touch/baton/domain/common/response/PageResponse.java @@ -15,14 +15,34 @@ public static PageResponse of(final List respons return new PageResponse<>(limitResponses, PageInfo.normal(getLastElementId(limitResponses))); } - public record PageInfo(boolean isLast, Long nextCursor) { + public static PageResponse of(final List responses, + final PageParams pageParams, + final long total + ) { + final int limit = pageParams.limit(); + if (isLastPage(responses, limit)) { + return new PageResponse<>(responses, PageInfo.last(total)); + } + final List limitResponses = responses.subList(0, pageParams.getLimitForQuery()); + return new PageResponse<>(limitResponses, PageInfo.normal(getLastElementId(limitResponses), total)); + } + + public record PageInfo(boolean isLast, Long nextCursor, Long totalCount) { public static PageInfo last() { - return new PageInfo(true, null); + return new PageInfo(true, null, null); + } + + public static PageInfo last(final Long totalCount) { + return new PageInfo(true, null, totalCount); } public static PageInfo normal(final Long nextCursor) { - return new PageInfo(false, nextCursor); + return new PageInfo(false, nextCursor, null); + } + + public static PageInfo normal(final Long nextCursor, final Long totalCount) { + return new PageInfo(false, nextCursor, totalCount); } } diff --git a/backend/baton/src/main/java/touch/baton/domain/common/vo/Page.java b/backend/baton/src/main/java/touch/baton/domain/common/vo/Page.java new file mode 100644 index 000000000..3c5163da7 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/common/vo/Page.java @@ -0,0 +1,6 @@ +package touch.baton.domain.common.vo; + +import java.util.List; + +public record Page(List contents, long total) { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java index d0ac63181..086afbc69 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java @@ -5,6 +5,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import touch.baton.domain.common.vo.Page; import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.domain.runnerpost.command.vo.ReviewStatus; import touch.baton.domain.tag.command.RunnerPostTag; @@ -26,7 +27,7 @@ public class RunnerPostPageRepository { private final JPAQueryFactory jpaQueryFactory; - public List pageByReviewStatusAndTagReducedName(final Long previousLastId, + public Page pageByReviewStatusAndTagReducedName(final Long previousLastId, final int limit, final TagReducedName tagReducedName, final ReviewStatus reviewStatus @@ -44,7 +45,16 @@ public List pageByReviewStatusAndTagReducedName(final Long previousL .where(tag.tagReducedName.eq(tagReducedName)); } - return query.fetch(); + final JPAQuery countQuery = jpaQueryFactory.selectFrom(runnerPost); + + if (tagReducedName != null) { + countQuery.leftJoin(runnerPost.runnerPostTags.runnerPostTags, runnerPostTag) + .leftJoin(runnerPostTag.tag, tag) + .where(tag.tagReducedName.eq(tagReducedName)); + } + + final long count = countQuery.stream().count(); + return new Page<>(query.fetch(), count); } public List pageBySupporterIdAndReviewStatus(final Long previousLastId, diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java index e711fea85..3210e499b 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryService.java @@ -5,6 +5,7 @@ import org.springframework.transaction.annotation.Transactional; import touch.baton.domain.common.request.PageParams; import touch.baton.domain.common.response.PageResponse; +import touch.baton.domain.common.vo.Page; import touch.baton.domain.member.command.Runner; import touch.baton.domain.member.command.SupporterRunnerPost; import touch.baton.domain.member.query.repository.SupporterRunnerPostQueryRepository; @@ -42,13 +43,13 @@ public PageResponse pageRunnerPostByTagNameAndReviewS final PageParams pageParams, final ReviewStatus reviewStatus ) { - final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(pageParams.cursor(), pageParams.getLimitForQuery(), TagReducedName.nullableInstance(tagName), reviewStatus); - final List runnerPostTags = runnerPostPageRepository.findRunnerPostTagsByRunnerPosts(runnerPosts); - final RunnerPostsApplicantCount runnerPostsApplicantCount = readRunnerPostsApplicantCount(runnerPosts); - final List responses = runnerPosts.stream() + final Page runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(pageParams.cursor(), pageParams.getLimitForQuery(), TagReducedName.nullableInstance(tagName), reviewStatus); + final List runnerPostTags = runnerPostPageRepository.findRunnerPostTagsByRunnerPosts(runnerPosts.contents()); + final RunnerPostsApplicantCount runnerPostsApplicantCount = readRunnerPostsApplicantCount(runnerPosts.contents()); + final List responses = runnerPosts.contents().stream() .map(runnerPost -> RunnerPostResponse.Simple.of(runnerPost, runnerPostsApplicantCount.getApplicantCountById(runnerPost.getId()), runnerPostTags)) .toList(); - return PageResponse.of(responses, pageParams); + return PageResponse.of(responses, pageParams, runnerPosts.total()); } public PageResponse pageRunnerPostBySupporterIdAndReviewStatus(final PageParams pageParams, diff --git a/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java index a5295d87a..fba68192c 100644 --- a/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java +++ b/backend/baton/src/test/java/touch/baton/assure/runnerpost/query/page/RunnerPostPageAssuredTest.java @@ -26,6 +26,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final Long 러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final long 전체_게시글_수 = 1; final RunnerPost 러너_게시글 = runnerPostRepository.getByRunnerPostId(러너_게시글_식별자값); final long 서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(러너_게시글_식별자값); @@ -39,7 +40,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { ReviewStatus.NOT_STARTED, List.of("자바", "스프링") ); - final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(전체_게시글_수); final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( List.of(기대된_러너_게시글_Simple_응답), 기대된_페이징_정보 @@ -63,6 +64,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { final Long 다음_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); final Long 이전_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final long 전체_게시글_수 = 2; final RunnerPost 다음_페이지_러너_게시글 = runnerPostRepository.getByRunnerPostId(다음_페이지_러너_게시글_식별자값); final long 다음_페이지_게시글_서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(다음_페이지_러너_게시글_식별자값); @@ -76,7 +78,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { ReviewStatus.NOT_STARTED, List.of("자바", "스프링") ); - final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(전체_게시글_수); final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( List.of(기대된_러너_게시글_Simple_응답), 기대된_페이징_정보 @@ -99,6 +101,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final Long 러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final long 전체_게시글_수 = 1; final RunnerPost 러너_게시글 = runnerPostRepository.getByRunnerPostId(러너_게시글_식별자값); final long 서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(러너_게시글_식별자값); @@ -112,7 +115,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { ReviewStatus.NOT_STARTED, List.of("자바", "스프링") ); - final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(전체_게시글_수); final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( List.of(기대된_러너_게시글_Simple_응답), 기대된_페이징_정보 @@ -136,6 +139,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { final Long 다음_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); final Long 이전_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final long 전체_게시글_수 = 2; final RunnerPost 다음_페이지_러너_게시글 = runnerPostRepository.getByRunnerPostId(다음_페이지_러너_게시글_식별자값); final long 다음_페이지_게시글_서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(다음_페이지_러너_게시글_식별자값); @@ -149,7 +153,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { ReviewStatus.NOT_STARTED, List.of("자바", "스프링") ); - final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(전체_게시글_수); final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( List.of(기대된_러너_게시글_Simple_응답), 기대된_페이징_정보 @@ -172,6 +176,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { final String 액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); final Long 러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final long 전체_게시글_수 = 1; final RunnerPost 러너_게시글 = runnerPostRepository.getByRunnerPostId(러너_게시글_식별자값); final long 서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(러너_게시글_식별자값); @@ -185,7 +190,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { ReviewStatus.NOT_STARTED, List.of("자바", "스프링") ); - final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(전체_게시글_수); final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( List.of(기대된_러너_게시글_Simple_응답), 기대된_페이징_정보 @@ -209,6 +214,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { final Long 다음_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); final Long 이전_페이지_러너_게시글_식별자값 = 러너_게시글_생성을_성공하고_러너_게시글_식별자값을_반환한다(액세스_토큰); + final long 전체_게시글_수 = 2; final RunnerPost 현재_페이지_러너_게시글 = runnerPostRepository.getByRunnerPostId(다음_페이지_러너_게시글_식별자값); final long 현재_페이지_게시글_서포터_지원자_수 = runnerPostRepository.countApplicantByRunnerPostId(다음_페이지_러너_게시글_식별자값); @@ -222,7 +228,7 @@ class RunnerPostPageAssuredTest extends AssuredTestConfig { ReviewStatus.NOT_STARTED, List.of("자바", "스프링") ); - final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(); + final PageResponse.PageInfo 기대된_페이징_정보 = PageResponse.PageInfo.last(전체_게시글_수); final PageResponse 기대된_러너_게시글_전체_Simple_페이징_응답 = 러너_게시글_전체_Simple_페이징_응답( List.of(기대된_러너_게시글_Simple_응답), 기대된_페이징_정보 diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java index bdc9f55aa..605f9f0c0 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadOfSupporterByGuestApiTest.java @@ -97,7 +97,8 @@ void readRunnerPostBySupporterIdAndReviewStatus() throws Exception { fieldWithPath("data.[].runnerProfile.imageUrl").type(STRING).description("러너 게시글의 러너 프로필 이미지"), fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), - fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서"), + fieldWithPath("pageInfo.totalCount").type(NUMBER).optional().description("전체 페이지 수").ignored() )) ); } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java index 7c272e67c..d6b254a40 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadSearchApiTest.java @@ -62,7 +62,7 @@ void readRunnerPostsByTagNamesAndReviewStatus() throws Exception { 0L, List.of(RunnerPostTagFixture.create(spyRunnerPost, javaTag), RunnerPostTagFixture.create(spyRunnerPost, springTag)) )); - final PageResponse pageResponse = PageResponse.of(responses, new PageParams(2L, 10)); + final PageResponse pageResponse = PageResponse.of(responses, new PageParams(2L, 10), 1); when(runnerPostQueryService.pageRunnerPostByTagNameAndReviewStatus(anyString(), any(PageParams.class), any(ReviewStatus.class))) .thenReturn(pageResponse); @@ -92,7 +92,8 @@ void readRunnerPostsByTagNamesAndReviewStatus() throws Exception { fieldWithPath("data.[].runnerProfile.imageUrl").type(STRING).description("러너 게시글의 러너 프로필 이미지"), fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), - fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서"), + fieldWithPath("pageInfo.totalCount").type(NUMBER).description("전체 페이지 수") )) ); } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java index ea878765a..8c4ed0641 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedRunnerApiTest.java @@ -108,7 +108,8 @@ void readRunnerPostByLoginedRunnerAndReviewStatus() throws Exception { .description("서포터 id (서포터가 존재할 때 NUMBER, 아닌 경우에 NULL)"), fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), - fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서"), + fieldWithPath("pageInfo.totalCount").type(NUMBER).optional().description("전체 페이지 수").ignored() )) ); } diff --git a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java index ed7f0194f..bcd430885 100644 --- a/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java +++ b/backend/baton/src/test/java/touch/baton/document/runnerpost/read/RunnerPostReadWithLoginedSupporterApiTest.java @@ -110,7 +110,8 @@ void readRunnerPostByLoginedSupporterAndReviewStatus() throws Exception { fieldWithPath("data.[].runnerProfile.imageUrl").type(STRING).description("러너 게시글의 러너 프로필 이미지"), fieldWithPath("data.[].tags.[]").type(ARRAY).description("러너 게시글의 태그 목록"), fieldWithPath("pageInfo.isLast").type(BOOLEAN).description("마지막 페이지 여부"), - fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서") + fieldWithPath("pageInfo.nextCursor").type(NUMBER).optional().description("다음 커서"), + fieldWithPath("pageInfo.totalCount").type(NUMBER).optional().description("전체 페이지 수").ignored() )) ); } diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java index c1c1a589c..49df23e3c 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepositoryTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.common.vo.Page; import touch.baton.domain.member.command.Runner; import touch.baton.domain.member.command.Supporter; import touch.baton.domain.runnerpost.command.RunnerPost; @@ -22,7 +23,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.SoftAssertions.assertSoftly; -import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.*; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.DONE; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.IN_PROGRESS; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.NOT_STARTED; +import static touch.baton.domain.runnerpost.command.vo.ReviewStatus.OVERDUE; import static touch.baton.fixture.vo.DeadlineFixture.deadline; class RunnerPostPageRepositoryTest extends RepositoryTestConfig { @@ -49,15 +53,16 @@ void findByPageInfo() { em.close(); // when - final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, null, null); + final Page runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, null, null); runnerPostIds.sort(Comparator.reverseOrder()); final List expected = runnerPostIds.subList(1, 1 + limit); // then assertSoftly(softly -> { - softly.assertThat(runnerPosts).hasSize(limit); - softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + softly.assertThat(runnerPosts.contents()).hasSize(limit); + softly.assertThat(runnerPosts.contents().stream().mapToLong(RunnerPost::getId)) .isEqualTo(expected); + softly.assertThat(runnerPosts.total()).isEqualTo(persistSize); }); } @@ -78,15 +83,16 @@ void findLatestByLimit() { em.close(); // when - final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, null, null); + final Page runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, null, null); runnerPostIds.sort(Comparator.reverseOrder()); final List expected = runnerPostIds; // then assertSoftly(softly -> { - softly.assertThat(runnerPosts).hasSize(limit); - softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + softly.assertThat(runnerPosts.contents()).hasSize(limit); + softly.assertThat(runnerPosts.contents().stream().mapToLong(RunnerPost::getId)) .isEqualTo(expected); + softly.assertThat(runnerPosts.total()).isEqualTo(runnerPostCount); }); } @@ -109,15 +115,16 @@ void findByPageInfoAndReviewStatus() { em.close(); // when - final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, null, reviewStatus); + final Page runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, null, reviewStatus); runnerPostIds.sort(Comparator.reverseOrder()); final List expected = runnerPostIds.subList(1, 1 + limit); // then assertSoftly(softly -> { - softly.assertThat(runnerPosts).hasSize(limit); - softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + softly.assertThat(runnerPosts.contents()).hasSize(limit); + softly.assertThat(runnerPosts.contents().stream().mapToLong(RunnerPost::getId)) .isEqualTo(expected); + softly.assertThat(runnerPosts.total()).isEqualTo(persistSize); }); } @@ -138,15 +145,16 @@ void findLatestByLimitAndReviewStatus() { em.close(); // when - final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, null, reviewStatus); + final Page runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, null, reviewStatus); runnerPostIds.sort(Comparator.reverseOrder()); final List expected = runnerPostIds; // then assertSoftly(softly -> { - softly.assertThat(runnerPosts).hasSize(limit); - softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + softly.assertThat(runnerPosts.contents()).hasSize(limit); + softly.assertThat(runnerPosts.contents().stream().mapToLong(RunnerPost::getId)) .isEqualTo(expected); + softly.assertThat(runnerPosts.total()).isEqualTo(runnerPostCount); }); } @@ -176,15 +184,16 @@ void findByPageInfoAndReviewStatusAndTagReducedName() { // when final TagReducedName tagReducedName = TagReducedName.from(tagName); - final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, tagReducedName, reviewStatus); + final Page runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(previousLastId, limit, tagReducedName, reviewStatus); runnerPostIds.sort(Comparator.reverseOrder()); final List expected = runnerPostIds.subList(1, 1 + limit); // then assertSoftly(softly -> { - softly.assertThat(runnerPosts).hasSize(limit); - softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + softly.assertThat(runnerPosts.contents()).hasSize(limit); + softly.assertThat(runnerPosts.contents().stream().mapToLong(RunnerPost::getId)) .isEqualTo(expected); + softly.assertThat(runnerPosts.total()).isEqualTo(persistSize); }); } @@ -199,7 +208,8 @@ void findLatestByLimitAndTagNameAndReviewStatus() { final Tag tag = persistTag(tagName); final List runnerPostIds = new ArrayList<>(); - for (int i = 0; i < 10; i++) { + final int persistRunnerPostCount = 10; + for (int i = 0; i < persistRunnerPostCount; i++) { final RunnerPost runnerPost = persistRunnerPost(runner, reviewStatus); persistRunnerPostTag(runnerPost, tag); runnerPostIds.add(runnerPost.getId()); @@ -211,15 +221,16 @@ void findLatestByLimitAndTagNameAndReviewStatus() { // when final TagReducedName tagReducedName = TagReducedName.from(tagName); - final List runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, tagReducedName, reviewStatus); + final Page runnerPosts = runnerPostPageRepository.pageByReviewStatusAndTagReducedName(null, limit, tagReducedName, reviewStatus); runnerPostIds.sort(Comparator.reverseOrder()); final List expected = runnerPostIds; // then assertSoftly(softly -> { - softly.assertThat(runnerPosts).hasSize(limit); - softly.assertThat(runnerPosts.stream().mapToLong(RunnerPost::getId)) + softly.assertThat(runnerPosts.contents()).hasSize(limit); + softly.assertThat(runnerPosts.contents().stream().mapToLong(RunnerPost::getId)) .isEqualTo(expected); + softly.assertThat(runnerPosts.total()).isEqualTo(persistRunnerPostCount); }); } diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java index be09c19ca..d291336ed 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java @@ -77,6 +77,7 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_firstPage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + final int totalJavaTagPost = 3; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -122,7 +123,8 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_firstPage() { final PageResponse expected = PageResponse.of( List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + totalJavaTagPost ); // then @@ -138,6 +140,7 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_middlePage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + final int totalJavaTagPost = 4; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -160,17 +163,17 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_middlePage() { NOT_STARTED )); - runnerPostQueryRepository.save(RunnerPostFixture.create( + final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, deadline(now().plusHours(100)), - List.of(springTag), + List.of(javaTag), NOT_STARTED )); - final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, deadline(now().plusHours(100)), - List.of(javaTag), + List.of(springTag), NOT_STARTED )); @@ -190,7 +193,8 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_middlePage() { final PageResponse expected = PageResponse.of( List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + totalJavaTagPost ); // then @@ -206,6 +210,7 @@ void readRunnerPostByPageInfoAndReviewStatus_firstPage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + final int totalPost = 4; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -249,11 +254,13 @@ void readRunnerPostByPageInfoAndReviewStatus_firstPage() { RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag), RunnerPostTagFixture.create(expectedRunnerPostThree, springTag) ); + final PageResponse expected = PageResponse.of( List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + totalPost ); // then @@ -269,6 +276,7 @@ void readRunnerPostByPageInfoAndReviewStatus_middlePage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + final int totalPost = 5; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -323,8 +331,10 @@ void readRunnerPostByPageInfoAndReviewStatus_middlePage() { List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + totalPost ); + // then assertThat(actual).isEqualTo(expected); } @@ -373,10 +383,12 @@ void readRunnerPostByPageInfoAndTagName_firstPage() { RunnerPostTagFixture.create(expectedRunnerPostOne, springTag), RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag) ); + final int total = 2; final PageResponse expected = PageResponse.of( List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + total ); // then @@ -392,6 +404,7 @@ void readRunnerPostByPageInfoAndTagName_middlePage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + final int totalJavaTagPost = 3; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -407,18 +420,18 @@ void readRunnerPostByPageInfoAndTagName_middlePage() { ReviewStatus.OVERDUE )); - runnerPostQueryRepository.save(RunnerPostFixture.create( + final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, deadline(now().plusHours(100)), - List.of(springTag), - ReviewStatus.IN_PROGRESS + List.of(javaTag), + NOT_STARTED )); - final RunnerPost previousRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.create( + runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, deadline(now().plusHours(100)), - List.of(javaTag), - NOT_STARTED + List.of(springTag), + ReviewStatus.IN_PROGRESS )); // when @@ -437,7 +450,8 @@ void readRunnerPostByPageInfoAndTagName_middlePage() { final PageResponse expected = PageResponse.of( List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + totalJavaTagPost ); // then @@ -489,11 +503,13 @@ void readRunnerPostByPageInfo_firstPage() { RunnerPostTagFixture.create(expectedRunnerPostTwo, javaTag), RunnerPostTagFixture.create(expectedRunnerPostThree, springTag) ); + final int total = 3; final PageResponse expected = PageResponse.of( List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + total ); // then @@ -509,6 +525,7 @@ void readRunnerPostByPageInfo_middlePage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); + final int totalPost = 4; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -556,7 +573,8 @@ void readRunnerPostByPageInfo_middlePage() { List.of(RunnerPostResponse.Simple.of(expectedRunnerPostThree, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), - pageParams + pageParams, + totalPost ); // then @@ -759,9 +777,6 @@ void existsRunnerPostApplicantByRunnerPostIdAndMemberId() { @Test void existsRunnerPostApplicantByRunnerPostIdAndMemberId_if_runnerPost_is_not_exist_then_return_false() { // given - final Member savedMemberDitoo = memberCommandRepository.save(MemberFixture.createDitoo()); - final Runner savedRunnerDitoo = runnerQueryRepository.save(RunnerFixture.createRunner(savedMemberDitoo)); - final Member savedMemberHyena = memberCommandRepository.save(MemberFixture.createHyena()); // when From be6041af57f0010d7862debb7c30fa09e7036acf Mon Sep 17 00:00:00 2001 From: Ethan Date: Sat, 17 Feb 2024 19:53:48 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=90=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=EA=B0=80=20=EB=A7=8E=EC=9D=80=20=EC=84=9C=ED=8F=AC?= =?UTF-8?q?=ED=84=B0=20=EC=88=9C=EB=8C=80=EB=A1=9C=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#712)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 리뷰가 많은 사람 랭킹 구하는 기능 구현 * test: 랭킹 인수테스트와 restdocs 테스트 구현 * feat: 랭킹에 company 항목 추가 * refactor: 다른 랭킹도 추가할 수 있도록 Rankable 마커 인터페이스 구현 --- backend/baton/secret | 2 +- .../baton/src/docs/asciidoc/RankReadAPI.adoc | 29 +++++++ backend/baton/src/docs/asciidoc/index.adoc | 5 ++ .../query/controller/RankQueryController.java | 23 +++++ .../controller/response/RankResponses.java | 41 +++++++++ .../query/controller/response/Rankable.java | 4 + .../repository/RankQuerydslRepository.java | 26 ++++++ .../query/service/RankQueryService.java | 19 ++++ .../query/RankQueryRestAssuredTest.java | 45 ++++++++++ .../query/RankQueryAssuredSupport.java | 71 +++++++++++++++ .../repository/TestRankQueryRepository.java | 31 +++++++ .../TestSupporterQueryRepository.java | 5 +- .../touch/baton/config/AssuredTestConfig.java | 4 + .../config/QueryDslRepositoryTestConfig.java | 6 ++ .../baton/config/RepositoryTestConfig.java | 21 ++++- .../touch/baton/config/RestdocsConfig.java | 8 +- .../touch/baton/config/ServiceTestConfig.java | 4 + .../baton/document/rank/RankReadApiTest.java | 61 +++++++++++++ .../RankQuerydslRepositoryTest.java | 40 +++++++++ .../query/service/RankQueryServiceTest.java | 87 +++++++++++++++++++ .../baton/fixture/domain/MemberFixture.java | 11 +++ .../fixture/domain/SupporterFixture.java | 10 +++ 22 files changed, 547 insertions(+), 6 deletions(-) create mode 100644 backend/baton/src/docs/asciidoc/RankReadAPI.adoc create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/controller/RankQueryController.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/RankResponses.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/Rankable.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java create mode 100644 backend/baton/src/main/java/touch/baton/domain/member/query/service/RankQueryService.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/member/query/RankQueryRestAssuredTest.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/member/support/query/RankQueryAssuredSupport.java create mode 100644 backend/baton/src/test/java/touch/baton/assure/repository/TestRankQueryRepository.java create mode 100644 backend/baton/src/test/java/touch/baton/document/rank/RankReadApiTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java create mode 100644 backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java diff --git a/backend/baton/secret b/backend/baton/secret index 9a4adb562..4223f4655 160000 --- a/backend/baton/secret +++ b/backend/baton/secret @@ -1 +1 @@ -Subproject commit 9a4adb562b9e9d299f21d6fc8ca457078a8eaf60 +Subproject commit 4223f46551ffa30795cb8e7ea9353c5932939cf5 diff --git a/backend/baton/src/docs/asciidoc/RankReadAPI.adoc b/backend/baton/src/docs/asciidoc/RankReadAPI.adoc new file mode 100644 index 000000000..eefcc7502 --- /dev/null +++ b/backend/baton/src/docs/asciidoc/RankReadAPI.adoc @@ -0,0 +1,29 @@ +ifndef::snippets[] +:snippets: ../../../build/generated-snippets +endif::[] +:doctype: book +:icons: font +:source-highlighter: highlight.js +:toc: left +:toclevels: 3 +:sectlinks: +:operation-http-request-title: Example Request +:operation-http-response-title: Example Response + +==== *완료된 리뷰가 많은 서포터 랭킹 조회 API* + +===== *Http Request* + +include::{snippets}/../../build/generated-snippets/rank-read-api-test/read-most-review-supporter/http-request.adoc[] + +===== *Http Request Query Parameters* + +include::{snippets}/../../build/generated-snippets/rank-read-api-test/read-most-review-supporter/query-parameters.adoc[] + +===== *Http Response* + +include::{snippets}/../../build/generated-snippets/rank-read-api-test/read-most-review-supporter/http-response.adoc[] + +===== *Http Response Fields* + +include::{snippets}/../../build/generated-snippets/rank-read-api-test/read-most-review-supporter/response-fields.adoc[] diff --git a/backend/baton/src/docs/asciidoc/index.adoc b/backend/baton/src/docs/asciidoc/index.adoc index ce5430092..c9ebef982 100644 --- a/backend/baton/src/docs/asciidoc/index.adoc +++ b/backend/baton/src/docs/asciidoc/index.adoc @@ -78,3 +78,8 @@ include::NotificationUpdateApi.adoc[] === *알림 삭제* include::NotificationDeleteApi.adoc[] + +== *[ 랭킹 ]* + +=== *랭킹 조회* +include::RankReadAPI.adoc[] diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/controller/RankQueryController.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/RankQueryController.java new file mode 100644 index 000000000..25bc3482d --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/RankQueryController.java @@ -0,0 +1,23 @@ +package touch.baton.domain.member.query.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import touch.baton.domain.member.query.controller.response.RankResponses; +import touch.baton.domain.member.query.service.RankQueryService; + +@RequiredArgsConstructor +@RequestMapping("/api/v1/rank") +@RestController +public class RankQueryController { + + private final RankQueryService rankQueryService; + + @GetMapping("/supporter") + public ResponseEntity readMostReviewSupporter(@RequestParam(defaultValue = "5") final int limit) { + return ResponseEntity.ok(rankQueryService.readMostReviewSupporter(limit)); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/RankResponses.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/RankResponses.java new file mode 100644 index 000000000..8f2b85ed9 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/RankResponses.java @@ -0,0 +1,41 @@ +package touch.baton.domain.member.query.controller.response; + +import touch.baton.domain.member.command.Supporter; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public record RankResponses(List data) { + + public static RankResponses from(final List supporters) { + final AtomicInteger rank = new AtomicInteger(1); + final List responses = supporters.stream() + .map(supporter -> new SupporterRank( + rank.getAndIncrement(), + supporter.getMember().getMemberName().getValue(), + supporter.getId(), + supporter.getReviewCount().getValue(), + supporter.getMember().getImageUrl().getValue(), + supporter.getMember().getGithubUrl().getValue(), + supporter.getMember().getCompany().getValue(), + supporter.getSupporterTechnicalTags().getSupporterTechnicalTags().stream() + .map(supporterTechnicalTag -> supporterTechnicalTag.getTechnicalTag().getTagName().getValue()) + .toList())) + .toList(); + + return new RankResponses<>(responses); + } + + public record SupporterRank( + int rank, + String name, + long supporterId, + int reviewedCount, + String imageUrl, + String githubUrl, + String company, + List technicalTags + ) implements Rankable { + + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/Rankable.java b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/Rankable.java new file mode 100644 index 000000000..9fdebd4d2 --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/controller/response/Rankable.java @@ -0,0 +1,4 @@ +package touch.baton.domain.member.query.controller.response; + +public interface Rankable { +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java new file mode 100644 index 000000000..06f741d2b --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java @@ -0,0 +1,26 @@ +package touch.baton.domain.member.query.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import touch.baton.domain.member.command.Supporter; + +import java.util.List; + +import static touch.baton.domain.member.command.QMember.member; +import static touch.baton.domain.member.command.QSupporter.supporter; + +@RequiredArgsConstructor +@Repository +public class RankQuerydslRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findMostReviewSupporterByCount(final int count) { + return jpaQueryFactory.selectFrom(supporter) + .join(supporter.member, member).fetchJoin() + .orderBy(supporter.reviewCount.value.desc()) + .limit(count) + .fetch(); + } +} diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/service/RankQueryService.java b/backend/baton/src/main/java/touch/baton/domain/member/query/service/RankQueryService.java new file mode 100644 index 000000000..106493efe --- /dev/null +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/service/RankQueryService.java @@ -0,0 +1,19 @@ +package touch.baton.domain.member.query.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.member.query.controller.response.RankResponses; +import touch.baton.domain.member.query.repository.RankQuerydslRepository; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class RankQueryService { + + private final RankQuerydslRepository rankQueryDslRepository; + + public RankResponses readMostReviewSupporter(final int maxCount) { + return RankResponses.from(rankQueryDslRepository.findMostReviewSupporterByCount(maxCount)); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/member/query/RankQueryRestAssuredTest.java b/backend/baton/src/test/java/touch/baton/assure/member/query/RankQueryRestAssuredTest.java new file mode 100644 index 000000000..3407ac704 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/member/query/RankQueryRestAssuredTest.java @@ -0,0 +1,45 @@ +package touch.baton.assure.member.query; + +import org.junit.jupiter.api.Test; +import touch.baton.assure.member.support.query.RankQueryAssuredSupport; +import touch.baton.config.AssuredTestConfig; +import touch.baton.config.infra.auth.oauth.authcode.FakeAuthCodes; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.SocialId; + +import java.util.List; + +import static touch.baton.assure.member.support.query.RankQueryAssuredSupport.RankQueryResponseBuilder.서포터_리뷰_랭킹_응답; + +@SuppressWarnings("NonAsciiCharacters") +class RankQueryRestAssuredTest extends AssuredTestConfig { + + @Test + void 코드_리뷰를_가장_많이_한_5명을_리뷰_숫자를_기준으로_내림차순으로_반환한다() { + // given + final String 에단_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ethanAuthCode()); + final SocialId 에단_소셜_아이디 = jwtTestManager.parseToSocialId(에단_액세스_토큰); + final Supporter 서포터_에단 = supporterRepository.getBySocialId(에단_소셜_아이디); + rankQueryRepository.updateReviewCount(서포터_에단.getId(), 10); + + final String 디투_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.ditooAuthCode()); + final SocialId 디투_소셜_아이디 = jwtTestManager.parseToSocialId(디투_액세스_토큰); + final Supporter 서포터_디투 = supporterRepository.getBySocialId(디투_소셜_아이디); + rankQueryRepository.updateReviewCount(서포터_디투.getId(), 5); + + final String 헤나_액세스_토큰 = oauthLoginTestManager.소셜_회원가입을_진행한_후_액세스_토큰을_반환한다(FakeAuthCodes.hyenaAuthCode()); + final SocialId 헤나_소셜_아이디 = jwtTestManager.parseToSocialId(헤나_액세스_토큰); + final Supporter 서포터_헤나 = supporterRepository.getBySocialId(헤나_소셜_아이디); + rankQueryRepository.updateReviewCount(서포터_헤나.getId(), 20); + + // when, then + RankQueryAssuredSupport.클라이언트_요청() + .서포터_리뷰_랭킹을_조회한다(2) + + .서버_응답() + .서포터_리뷰_랭킹_조회_성공을_검증한다(List.of( + 서포터_리뷰_랭킹_응답(1, 서포터_헤나, 20), + 서포터_리뷰_랭킹_응답(2, 서포터_에단, 10) + )); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/member/support/query/RankQueryAssuredSupport.java b/backend/baton/src/test/java/touch/baton/assure/member/support/query/RankQueryAssuredSupport.java new file mode 100644 index 000000000..004608c04 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/member/support/query/RankQueryAssuredSupport.java @@ -0,0 +1,71 @@ +package touch.baton.assure.member.support.query; + +import io.restassured.common.mapper.TypeRef; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import touch.baton.assure.common.AssuredSupport; +import touch.baton.assure.common.QueryParams; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.query.controller.response.RankResponses; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@SuppressWarnings("NonAsciiCharacters") +public class RankQueryAssuredSupport { + + private RankQueryAssuredSupport() { + } + + public static RankQueryBuilder 클라이언트_요청() { + return new RankQueryBuilder(); + } + + public static class RankQueryBuilder { + + private ExtractableResponse response; + + public RankQueryBuilder 서포터_리뷰_랭킹을_조회한다(final int limit) { + response = AssuredSupport.get("/api/v1/rank/supporter", new QueryParams(Map.of("limit", limit))); + return this; + } + + public RankQueryResponseBuilder 서버_응답() { + return new RankQueryResponseBuilder(response); + } + } + + public static class RankQueryResponseBuilder { + + private final ExtractableResponse response; + + public RankQueryResponseBuilder(final ExtractableResponse response) { + this.response = response; + } + + public void 서포터_리뷰_랭킹_조회_성공을_검증한다(final List 랭킹_응답) { + final RankResponses actual = this.response.as(new TypeRef<>(){}); + + assertSoftly(softly -> { + softly.assertThat(actual.data()).hasSize(랭킹_응답.size()); + softly.assertThat(actual.data()).isEqualTo(랭킹_응답); + }); + } + + public static RankResponses.SupporterRank 서포터_리뷰_랭킹_응답(final int 순위, final Supporter 서포터, final int 리뷰수) { + final Member member = 서포터.getMember(); + return new RankResponses.SupporterRank(순위, + member.getMemberName().getValue(), + 서포터.getId(), + 리뷰수, + member.getImageUrl().getValue(), + member.getGithubUrl().getValue(), + member.getCompany().getValue(), + Collections.emptyList()); + } + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestRankQueryRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestRankQueryRepository.java new file mode 100644 index 000000000..b4f70b68d --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestRankQueryRepository.java @@ -0,0 +1,31 @@ +package touch.baton.assure.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import touch.baton.domain.member.query.repository.RankQuerydslRepository; + +import static touch.baton.domain.member.command.QSupporter.supporter; + +@Profile("test") +@Primary +@Repository +public class TestRankQueryRepository extends RankQuerydslRepository { + + private JPAQueryFactory jpaQueryFactory; + + public TestRankQueryRepository(final JPAQueryFactory jpaQueryFactory) { + super(jpaQueryFactory); + this.jpaQueryFactory = jpaQueryFactory; + } + + @Transactional + public void updateReviewCount(final long supporterId, final int reviewCount) { + jpaQueryFactory.update(supporter) + .set(supporter.reviewCount.value, supporter.reviewCount.value.add(reviewCount)) + .where(supporter.id.eq(supporterId)) + .execute(); + } +} diff --git a/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterQueryRepository.java b/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterQueryRepository.java index bb152099f..07da14068 100644 --- a/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterQueryRepository.java +++ b/backend/baton/src/test/java/touch/baton/assure/repository/TestSupporterQueryRepository.java @@ -16,9 +16,10 @@ default Supporter getBySocialId(final SocialId socialId) { } @Query(""" - select s, m + select s from Supporter s - join fetch Member m on m.id = s.member.id + join fetch s.member m + join fetch s.supporterTechnicalTags stt where m.socialId = :socialId """) Optional joinMemberBySocialId(@Param("socialId") final SocialId socialId); diff --git a/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java b/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java index 44d5d5f77..03f26b996 100644 --- a/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/AssuredTestConfig.java @@ -15,6 +15,7 @@ import touch.baton.assure.common.OauthLoginTestManager; import touch.baton.assure.repository.TestMemberQueryRepository; import touch.baton.assure.repository.TestNotificationCommandRepository; +import touch.baton.assure.repository.TestRankQueryRepository; import touch.baton.assure.repository.TestRefreshTokenRepository; import touch.baton.assure.repository.TestRunnerPostQueryRepository; import touch.baton.assure.repository.TestRunnerQueryRepository; @@ -61,6 +62,9 @@ public abstract class AssuredTestConfig { @Autowired protected JwtTestManager jwtTestManager; + @Autowired + protected TestRankQueryRepository rankQueryRepository; + protected OauthLoginTestManager oauthLoginTestManager = new OauthLoginTestManager(); @BeforeEach diff --git a/backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java b/backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java index a06536c83..4fee2ae83 100644 --- a/backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/QueryDslRepositoryTestConfig.java @@ -5,6 +5,7 @@ import jakarta.persistence.PersistenceContext; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import touch.baton.domain.member.query.repository.RankQuerydslRepository; import touch.baton.domain.notification.query.repository.NotificationQuerydslRepository; import touch.baton.domain.runnerpost.query.repository.RunnerPostPageRepository; import touch.baton.domain.tag.query.repository.TagQuerydslRepository; @@ -34,4 +35,9 @@ public NotificationQuerydslRepository notificationQuerydslRepository() { public TagQuerydslRepository tagQuerydslRepository() { return new TagQuerydslRepository(jpaQueryFactory()); } + + @Bean + public RankQuerydslRepository queryDslRepository() { + return new RankQuerydslRepository(jpaQueryFactory()); + } } diff --git a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java index 67728ef28..acf8a239d 100644 --- a/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/RepositoryTestConfig.java @@ -4,15 +4,17 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import touch.baton.domain.notification.command.Notification; -import touch.baton.domain.notification.command.vo.NotificationReferencedId; import touch.baton.domain.member.command.Member; import touch.baton.domain.member.command.Runner; import touch.baton.domain.member.command.Supporter; import touch.baton.domain.member.command.SupporterRunnerPost; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.notification.command.Notification; +import touch.baton.domain.notification.command.vo.NotificationReferencedId; import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.domain.tag.command.RunnerPostTag; import touch.baton.domain.tag.command.Tag; +import touch.baton.domain.technicaltag.command.TechnicalTag; import touch.baton.fixture.domain.NotificationFixture; import touch.baton.fixture.domain.RunnerFixture; import touch.baton.fixture.domain.RunnerPostFixture; @@ -22,6 +24,7 @@ import touch.baton.fixture.domain.TagFixture; import java.time.LocalDateTime; +import java.util.List; import static touch.baton.fixture.vo.DeadlineFixture.deadline; import static touch.baton.fixture.vo.TagNameFixture.tagName; @@ -52,6 +55,20 @@ protected Supporter persistSupporter(final Member member) { return supporter; } + protected Supporter persistSupporter(final ReviewCount reviewCount, final Member member) { + em.persist(member); + final Supporter supporter = SupporterFixture.create(reviewCount, member); + em.persist(supporter); + return supporter; + } + + protected Supporter persistSupporter(final ReviewCount reviewCount, final Member member, final List technicalTags) { + em.persist(member); + final Supporter supporter = SupporterFixture.create(reviewCount, member, technicalTags); + em.persist(supporter); + return supporter; + } + protected RunnerPost persistRunnerPost(final Runner runner) { final RunnerPost runnerPost = RunnerPostFixture.create(runner, deadline(LocalDateTime.now().plusHours(100))); em.persist(runnerPost); diff --git a/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java b/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java index 42ccfa96f..153cac48a 100644 --- a/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/RestdocsConfig.java @@ -24,8 +24,10 @@ import touch.baton.domain.member.command.service.RunnerCommandService; import touch.baton.domain.member.command.service.SupporterCommandService; import touch.baton.domain.member.query.controller.MemberQueryController; +import touch.baton.domain.member.query.controller.RankQueryController; import touch.baton.domain.member.query.controller.RunnerQueryController; import touch.baton.domain.member.query.controller.SupporterQueryController; +import touch.baton.domain.member.query.service.RankQueryService; import touch.baton.domain.member.query.service.RunnerQueryService; import touch.baton.domain.member.query.service.SupporterQueryService; import touch.baton.domain.notification.command.controller.NotificationCommandController; @@ -66,7 +68,8 @@ MemberBranchController.class, OauthCommandController.class, NotificationCommandController.class, - NotificationQueryController.class + NotificationQueryController.class, + RankQueryController.class }) @Import({RestDocsResultConfig.class}) public abstract class RestdocsConfig { @@ -127,6 +130,9 @@ public abstract class RestdocsConfig { @MockBean protected NotificationCommandService notificationCommandService; + @MockBean + protected RankQueryService rankQueryService; + @BeforeEach void restdocsSetUp(final WebApplicationContext webApplicationContext) { this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) diff --git a/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java b/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java index 647434219..4fd1bf99e 100644 --- a/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java +++ b/backend/baton/src/test/java/touch/baton/config/ServiceTestConfig.java @@ -6,6 +6,7 @@ import touch.baton.domain.member.command.repository.MemberCommandRepository; import touch.baton.domain.member.command.repository.SupporterCommandRepository; import touch.baton.domain.member.command.repository.SupporterRunnerPostCommandRepository; +import touch.baton.domain.member.query.repository.RankQuerydslRepository; import touch.baton.domain.member.query.repository.RunnerQueryRepository; import touch.baton.domain.member.query.repository.SupporterQueryRepository; import touch.baton.domain.member.query.repository.SupporterRunnerPostQueryRepository; @@ -77,6 +78,9 @@ public abstract class ServiceTestConfig extends RepositoryTestConfig { @Autowired protected NotificationQuerydslRepository notificationQuerydslRepository; + @Autowired + protected RankQuerydslRepository rankQuerydslRepository; + @Autowired protected ApplicationEventPublisher publisher; } diff --git a/backend/baton/src/test/java/touch/baton/document/rank/RankReadApiTest.java b/backend/baton/src/test/java/touch/baton/document/rank/RankReadApiTest.java new file mode 100644 index 000000000..2872b97f4 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/document/rank/RankReadApiTest.java @@ -0,0 +1,61 @@ +package touch.baton.document.rank; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.RestdocsConfig; +import touch.baton.domain.member.query.controller.response.RankResponses; + +import java.util.List; + +import static org.mockito.Mockito.when; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; +import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; +import static org.springframework.restdocs.payload.JsonFieldType.STRING; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class RankReadApiTest extends RestdocsConfig { + + @DisplayName("완료된 리뷰가 많은 서포터 랭킹 조회 API") + @Test + void readMostReviewSupporter() throws Exception { + // given + final List response = List.of( + new RankResponses.SupporterRank(1, "ethan", 1L, 10, "imageUrl", "githubUrl", "우아한테크코스", List.of("java", "spring")), + new RankResponses.SupporterRank(2, "hyena", 2L, 9, "imageUrl", "githubUrl", "우아한테크코스", List.of("java", "spring")), + new RankResponses.SupporterRank(3, "ditoo", 3L, 8, "imageUrl", "githubUrl", "우아한테크코스", List.of("java", "spring")), + new RankResponses.SupporterRank(4, "aiden", 4L, 7, "imageUrl", "githubUrl", "우아한테크코스", List.of("javascript", "react")), + new RankResponses.SupporterRank(5, "ditoo", 5L, 6, "imageUrl", "githubUrl", "우아한테크코스", List.of("javascript", "react")) + ); + + // when + when(rankQueryService.readMostReviewSupporter(5)).thenReturn(new RankResponses(response)); + + // then + mockMvc.perform(get("/api/v1/rank/supporter") + .queryParam("limit", "5")) + .andExpect(status().isOk()) + .andExpect(content().contentType(APPLICATION_JSON)) + .andDo(restDocs.document( + queryParameters( + parameterWithName("limit").description("조회할 랭킹 개수") + ), + responseFields( + fieldWithPath("data.[].rank").type(NUMBER).description("서포터 랭킹"), + fieldWithPath("data.[].name").type(STRING).description("서포터 이름"), + fieldWithPath("data.[].supporterId").type(NUMBER).description("서포터 아이디"), + fieldWithPath("data.[].reviewedCount").type(NUMBER).description("서포터가 완료한 리뷰 수"), + fieldWithPath("data.[].imageUrl").type(STRING).description("서포터 프로필 이미지 URL"), + fieldWithPath("data.[].githubUrl").type(STRING).description("서포터 깃허브 URL"), + fieldWithPath("data.[].company").type(STRING).description("서포터 소속"), + fieldWithPath("data.[].technicalTags").type(ARRAY).description("서포터 기술 태그 목록") + ) + )); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java new file mode 100644 index 000000000..0097ee79e --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java @@ -0,0 +1,40 @@ +package touch.baton.domain.member.query.repository; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import touch.baton.config.RepositoryTestConfig; +import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.fixture.domain.MemberFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class RankQuerydslRepositoryTest extends RepositoryTestConfig { + + @Autowired + private RankQuerydslRepository rankQueryDslRepository; + + @DisplayName("Supporter의 ReviewCount가 높은 순으로 count 개수 만큼 조회한다.") + @Test + void findMostReviewSupporterByCount() { + // given + persistSupporter(new ReviewCount(4), MemberFixture.createEthan()); + persistSupporter(new ReviewCount(3), MemberFixture.createHyena()); + persistSupporter(new ReviewCount(8), MemberFixture.createDitoo()); + + // when + final int count = 2; + final List actual = rankQueryDslRepository.findMostReviewSupporterByCount(count); + + // then + assertAll( + () -> assertThat(actual).hasSize(count), + () -> assertThat(actual.get(0).getReviewCount().getValue()).isEqualTo(8), + () -> assertThat(actual.get(1).getReviewCount().getValue()).isEqualTo(4) + ); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java new file mode 100644 index 000000000..306518e61 --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java @@ -0,0 +1,87 @@ +package touch.baton.domain.member.query.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.config.ServiceTestConfig; +import touch.baton.domain.member.command.Member; +import touch.baton.domain.member.command.vo.ReviewCount; +import touch.baton.domain.member.query.controller.response.RankResponses; +import touch.baton.domain.technicaltag.command.TechnicalTag; +import touch.baton.fixture.domain.MemberFixture; +import touch.baton.fixture.domain.TechnicalTagFixture; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static touch.baton.fixture.vo.MemberNameFixture.memberName; + +class RankQueryServiceTest extends ServiceTestConfig { + + private RankQueryService rankQueryService; + + @BeforeEach + void setUp() { + rankQueryService = new RankQueryService(rankQuerydslRepository); + } + + @DisplayName("Supporter의 ReviewCount가 높은 순으로 count 개수 만큼 조회한다.") + @Test + void readMostReviewSupporter() { + // given + + for (int reviewedCount = 0; reviewedCount < 5; reviewedCount++) { + persistSupporter(new ReviewCount(reviewedCount), MemberFixture.create(memberName("member" + reviewedCount))); + } + + final TechnicalTag javaTag = technicalTagQueryRepository.save(TechnicalTagFixture.createJava()); + final TechnicalTag springTag = technicalTagQueryRepository.save(TechnicalTagFixture.createSpring()); + final TechnicalTag reactTag = technicalTagQueryRepository.save(TechnicalTagFixture.createReact()); + + final Member fourthRankMember = persistMember(MemberFixture.create(memberName("fourth"))); + final List fourthRankMembersTechnicalTags = List.of(javaTag, springTag); + persistSupporter(new ReviewCount(12), fourthRankMember, fourthRankMembersTechnicalTags); + + final Member thirdRankMember = persistMember(MemberFixture.create(memberName("third"))); + final List thirdRankMembersTechnicalTags = List.of(javaTag, reactTag); + persistSupporter(new ReviewCount(13), thirdRankMember, thirdRankMembersTechnicalTags); + + final Member firstRankMember = persistMember(MemberFixture.create(memberName("first"))); + final List firstRankMembersTechnicalTags = List.of(javaTag, springTag); + persistSupporter(new ReviewCount(15), firstRankMember, firstRankMembersTechnicalTags); + + final Member secondRankMember = persistMember(MemberFixture.create(memberName("second"))); + final List secondRankMembersTechnicalTags = List.of(javaTag); + persistSupporter(new ReviewCount(14), secondRankMember, secondRankMembersTechnicalTags); + + final Member fifthRankMember = persistMember(MemberFixture.create(memberName("fifth"))); + final List fifthMembersTechnicalTags = List.of(reactTag); + persistSupporter(new ReviewCount(11), fifthRankMember, fifthMembersTechnicalTags); + + // when + final int maxCount = 5; + final RankResponses actual = rankQueryService.readMostReviewSupporter(maxCount); + + // then + assertAll( + () -> assertResponse(actual.data().get(0), 1, "first", 15), + () -> assertResponse(actual.data().get(1), 2, "second", 14), + () -> assertResponse(actual.data().get(2), 3, "third", 13), + () -> assertResponse(actual.data().get(3), 4, "fourth", 12), + () -> assertResponse(actual.data().get(4), 5, "fifth", 11) + ); + } + + private static void assertResponse(final RankResponses.SupporterRank response, final int rank, final String memberName, final int reviewCount) { + assertAll( + () -> assertThat(response.rank()).isEqualTo(rank), + () -> assertThat(response.name()).isEqualTo(memberName), + () -> assertThat(response.reviewedCount()).isEqualTo(reviewCount), + () -> assertThat(response.supporterId()).isNotZero(), + () -> assertThat(response.imageUrl()).isNotBlank(), + () -> assertThat(response.githubUrl()).isNotBlank(), + () -> assertThat(response.technicalTags()).isNotEmpty()) + ; + } +} diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java index feeeec77c..a9052c813 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/MemberFixture.java @@ -37,6 +37,17 @@ public static Member create(final MemberName memberName, .build(); } + public static Member create(final MemberName memberName) { + return Member.builder() + .memberName(memberName) + .socialId(socialId(String.format("%s socialId", memberName.getValue()))) + .oauthId(oauthId(String.format("oauth_%s", memberName.getValue()))) + .githubUrl(githubUrl("https://github.com/")) + .company(company("우아한테크코스 5기 백엔드")) + .imageUrl(imageUrl("https://")) + .build(); + } + public static Member createHyena() { return create( memberName("헤나"), diff --git a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java index 903fd8196..8d832e503 100644 --- a/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java +++ b/backend/baton/src/test/java/touch/baton/fixture/domain/SupporterFixture.java @@ -24,6 +24,16 @@ public static Supporter create(final Member member) { .build(); } + public static Supporter create(final ReviewCount reviewCount, + final Member member + ) { + return Supporter.builder() + .reviewCount(reviewCount) + .member(member) + .supporterTechnicalTags(new SupporterTechnicalTags(new ArrayList<>())) + .build(); + } + public static Supporter create(final ReviewCount reviewCount, final Member member, final SupporterTechnicalTags supporterTechnicalTags From 8281ea66f239cba7e2320904a918647b1046837d Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 19 Feb 2024 23:23:23 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20review=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=EC=8B=9C=20reviewCount=20=EC=A6=9D=EA=B0=80=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#721)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: review 완료 시 reviewCount 증가 기능 구현 * test: reviewCount 증가로직 테스트 구현 * refactor: 리뷰수 증가 로직을 RunnerPost 안으로 옮김 --- .../domain/member/command/Supporter.java | 4 ++ .../domain/member/command/vo/ReviewCount.java | 4 ++ .../domain/runnerpost/command/RunnerPost.java | 1 + .../service/RunnerPostCommandService.java | 4 +- .../domain/member/vo/ReviewCountTest.java | 24 ++++++++++ .../domain/runnerpost/RunnerPostTest.java | 46 +++++++++++++++++-- .../RunnerPostCommandServiceCreateTest.java | 1 + .../RunnerPostCommandServiceDeleteTest.java | 1 + .../RunnerPostCommandServiceEventTest.java | 1 + .../RunnerPostCommandServiceUpdateTest.java | 4 ++ ...UpdateApplicantCancelationServiceTest.java | 1 + 11 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 backend/baton/src/test/java/touch/baton/domain/member/vo/ReviewCountTest.java diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/Supporter.java b/backend/baton/src/main/java/touch/baton/domain/member/command/Supporter.java index dc2473e77..7f4bf8eef 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/command/Supporter.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/Supporter.java @@ -120,6 +120,10 @@ public void updateCompany(final Company company) { this.member.updateCompany(company); } + public void increaseReviewCount() { + this.reviewCount.increase(); + } + @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ReviewCount.java b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ReviewCount.java index 958b89132..eb524477a 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ReviewCount.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/command/vo/ReviewCount.java @@ -24,4 +24,8 @@ public class ReviewCount { public ReviewCount(final int value) { this.value = value; } + + public void increase() { + value += 1; + } } diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java index bc5b983bc..86e2d2721 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/RunnerPost.java @@ -210,6 +210,7 @@ public void addAllRunnerPostTags(final List postTags) { public void finishReview() { updateReviewStatus(ReviewStatus.DONE); + this.supporter.increaseReviewCount(); } public void finishFeedback() { diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java index 6d6d2a292..0a29c58dc 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandService.java @@ -20,6 +20,7 @@ import touch.baton.domain.runnerpost.command.service.dto.RunnerPostApplicantCreateRequest; import touch.baton.domain.runnerpost.command.service.dto.RunnerPostCreateRequest; import touch.baton.domain.runnerpost.command.service.dto.RunnerPostUpdateRequest; +import touch.baton.domain.runnerpost.query.repository.RunnerPostQueryRepository; import touch.baton.domain.tag.command.RunnerPostTag; import touch.baton.domain.tag.command.Tag; import touch.baton.domain.tag.command.repository.TagCommandRepository; @@ -34,6 +35,7 @@ public class RunnerPostCommandService { private final RunnerPostCommandRepository runnerPostCommandRepository; + private final RunnerPostQueryRepository runnerPostQueryRepository; private final TagCommandRepository tagCommandRepository; private final SupporterCommandRepository supporterCommandRepository; private final SupporterRunnerPostCommandRepository supporterRunnerPostCommandRepository; @@ -125,7 +127,7 @@ public Long createRunnerPostApplicant(final Supporter supporter, } public void updateRunnerPostReviewStatusDone(final Long runnerPostId, final Supporter supporter) { - final RunnerPost foundRunnerPost = runnerPostCommandRepository.findById(runnerPostId) + final RunnerPost foundRunnerPost = runnerPostQueryRepository.joinSupporterByRunnerPostId(runnerPostId) .orElseThrow(() -> new RunnerPostBusinessException("해당 식별자의 러너 게시글이 존재하지 않습니다.")); if (Objects.isNull(foundRunnerPost.getSupporter())) { diff --git a/backend/baton/src/test/java/touch/baton/domain/member/vo/ReviewCountTest.java b/backend/baton/src/test/java/touch/baton/domain/member/vo/ReviewCountTest.java new file mode 100644 index 000000000..06ec79a7a --- /dev/null +++ b/backend/baton/src/test/java/touch/baton/domain/member/vo/ReviewCountTest.java @@ -0,0 +1,24 @@ +package touch.baton.domain.member.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import touch.baton.domain.member.command.vo.ReviewCount; + +import static org.assertj.core.api.Assertions.assertThat; + +class ReviewCountTest { + + @DisplayName("increaseCount가 호출 되면 value가 1 증가한다.") + @Test + void increaseCount() { + // given + final int initialValue = 0; + final ReviewCount reviewCount = new ReviewCount(initialValue); + + // when + reviewCount.increase(); + + // then + assertThat(reviewCount.getValue()).isEqualTo(initialValue + 1); + } +} diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java index f92dbdb91..81f02f634 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/RunnerPostTest.java @@ -1,5 +1,6 @@ package touch.baton.domain.runnerpost; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -71,11 +72,16 @@ class RunnerPostTest { .runnerTechnicalTags(RunnerTechnicalTagsFixture.create(new ArrayList<>())) .build(); - private final Supporter supporter = Supporter.builder() - .reviewCount(new ReviewCount(10)) - .member(supporterMember) - .supporterTechnicalTags(new SupporterTechnicalTags(new ArrayList<>())) - .build(); + private Supporter supporter; + + @BeforeEach + void setUp() { + supporter = Supporter.builder() + .reviewCount(new ReviewCount(10)) + .member(supporterMember) + .supporterTechnicalTags(new SupporterTechnicalTags(new ArrayList<>())) + .build(); + } @DisplayName("runnerPostTags 전체를 추가할 수 있다.") @Test @@ -584,4 +590,34 @@ void fail_same_to_same(final ReviewStatus reviewStatus) { .isInstanceOf(RunnerPostDomainException.class); } } + + @DisplayName("리뷰완료가 되면 ReviewStatus가 Done으로 바뀌고 리뷰를 작성한 ReviewCount가 1 증가한다.") + @Test + void finishReview() { + // given + final ReviewCount originReviewCount = new ReviewCount(supporter.getReviewCount().getValue()); + final RunnerPost runnerPost = RunnerPost.builder() + .title(new Title("러너가 작성하는 리뷰 요청 게시글의 테스트 제목입니다.")) + .implementedContents(new ImplementedContents("안녕하세요. 테스트 내용입니다.")) + .curiousContents(new CuriousContents("궁금한 점입니다.")) + .postscriptContents(new PostscriptContents("잘 부탁드립니다.")) + .pullRequestUrl(new PullRequestUrl("https://github.com")) + .deadline(new Deadline(LocalDateTime.now().plusHours(100))) + .watchedCount(new WatchedCount(0)) + .reviewStatus(ReviewStatus.IN_PROGRESS) + .isReviewed(IsReviewed.notReviewed()) + .runner(runner) + .supporter(supporter) + .runnerPostTags(new RunnerPostTags(new ArrayList<>())) + .build(); + + // when + runnerPost.finishReview(); + + // then + assertAll( + () -> assertThat(runnerPost.getReviewStatus()).isEqualTo(ReviewStatus.DONE), + () -> assertThat(supporter.getReviewCount()).isEqualTo(new ReviewCount(originReviewCount.getValue() + 1)) + ); + } } diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceCreateTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceCreateTest.java index e069c2963..84d510b4e 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceCreateTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceCreateTest.java @@ -52,6 +52,7 @@ class RunnerPostCommandServiceCreateTest extends ServiceTestConfig { void setUp() { runnerPostCommandService = new RunnerPostCommandService( runnerPostCommandRepository, + runnerPostQueryRepository, tagCommandRepository, supporterCommandRepository, supporterRunnerPostCommandRepository, diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java index ea25d3e5b..54e574952 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceDeleteTest.java @@ -29,6 +29,7 @@ class RunnerPostCommandServiceDeleteTest extends ServiceTestConfig { void setUp() { runnerPostCommandService = new RunnerPostCommandService( runnerPostCommandRepository, + runnerPostQueryRepository, tagCommandRepository, supporterCommandRepository, supporterRunnerPostCommandRepository, diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java index 00042c60b..ae337c208 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceEventTest.java @@ -37,6 +37,7 @@ class RunnerPostCommandServiceEventTest extends ServiceTestConfig { void setUp() { runnerPostCommandService = new RunnerPostCommandService( runnerPostCommandRepository, + runnerPostQueryRepository, tagCommandRepository, supporterCommandRepository, supporterRunnerPostCommandRepository, diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceUpdateTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceUpdateTest.java index 4beaca789..083706095 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceUpdateTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostCommandServiceUpdateTest.java @@ -7,6 +7,7 @@ import touch.baton.domain.member.command.Member; import touch.baton.domain.member.command.Runner; import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; import touch.baton.domain.runnerpost.command.RunnerPost; import touch.baton.domain.runnerpost.command.exception.RunnerPostBusinessException; import touch.baton.domain.runnerpost.command.exception.RunnerPostDomainException; @@ -43,6 +44,7 @@ class RunnerPostCommandServiceUpdateTest extends ServiceTestConfig { void setUp() { runnerPostCommandService = new RunnerPostCommandService( runnerPostCommandRepository, + runnerPostQueryRepository, tagCommandRepository, supporterCommandRepository, supporterRunnerPostCommandRepository, @@ -143,6 +145,7 @@ void updateRunnerPostReviewStatusDone() { // given final IsReviewed isReviewed = IsReviewed.notReviewed(); final RunnerPost targetRunnerPost = runnerPostQueryRepository.save(RunnerPostFixture.createWithSupporter(runner, assignedSupporter, IN_PROGRESS, isReviewed)); + final ReviewCount originalReviewCount = new ReviewCount(assignedSupporter.getReviewCount().getValue()); // when runnerPostCommandService.updateRunnerPostReviewStatusDone(targetRunnerPost.getId(), assignedSupporter); @@ -152,6 +155,7 @@ void updateRunnerPostReviewStatusDone() { assertThat(maybeRunnerPost).isPresent(); final RunnerPost actualRunnerPost = maybeRunnerPost.get(); assertThat(actualRunnerPost.getReviewStatus()).isEqualTo(ReviewStatus.DONE); + assertThat(assignedSupporter.getReviewCount()).isEqualTo(new ReviewCount(originalReviewCount.getValue() + 1)); } @DisplayName("없는 게시글의 상태를 리뷰 완료로 변경할 수 없다.") diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostUpdateApplicantCancelationServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostUpdateApplicantCancelationServiceTest.java index 4d50c0ec3..ca267021b 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostUpdateApplicantCancelationServiceTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/command/service/RunnerPostUpdateApplicantCancelationServiceTest.java @@ -35,6 +35,7 @@ class RunnerPostUpdateApplicantCancelationServiceTest extends ServiceTestConfig void setUp() { runnerPostCommandService = new RunnerPostCommandService( runnerPostCommandRepository, + runnerPostQueryRepository, tagCommandRepository, supporterCommandRepository, supporterRunnerPostCommandRepository, From 3428bfe4de5d79fa5283fc95d5ecb027cb22ecfe Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 19 Feb 2024 23:26:44 +0900 Subject: [PATCH 8/8] =?UTF-8?q?reviewCount=EA=B0=80=200=EC=9D=B8=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=EC=9D=B4=20=EC=A1=B0=ED=9A=8C=EA=B0=80=20?= =?UTF-8?q?=EB=90=98=EB=A9=B4=20=EC=A0=9C=EC=99=B8=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84=20(#722)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: reviewCount가 0인 항목이 조회가 되면 제외되도록 구현 * refactor: count 할 때 reviewStatus가 포함되도록 변경 --- .../repository/RankQuerydslRepository.java | 2 + .../repository/RunnerPostPageRepository.java | 3 +- .../RankQuerydslRepositoryTest.java | 21 +++++++++ .../query/service/RankQueryServiceTest.java | 43 ++++++++++++++++++- .../service/RunnerPostQueryServiceTest.java | 16 +++---- 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java index 06f741d2b..0e5456587 100644 --- a/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/member/query/repository/RankQuerydslRepository.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import touch.baton.domain.member.command.Supporter; +import touch.baton.domain.member.command.vo.ReviewCount; import java.util.List; @@ -19,6 +20,7 @@ public class RankQuerydslRepository { public List findMostReviewSupporterByCount(final int count) { return jpaQueryFactory.selectFrom(supporter) .join(supporter.member, member).fetchJoin() + .where(supporter.reviewCount.ne(new ReviewCount(0))) .orderBy(supporter.reviewCount.value.desc()) .limit(count) .fetch(); diff --git a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java index 086afbc69..9e6566e49 100644 --- a/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java +++ b/backend/baton/src/main/java/touch/baton/domain/runnerpost/query/repository/RunnerPostPageRepository.java @@ -45,7 +45,8 @@ public Page pageByReviewStatusAndTagReducedName(final Long previousL .where(tag.tagReducedName.eq(tagReducedName)); } - final JPAQuery countQuery = jpaQueryFactory.selectFrom(runnerPost); + final JPAQuery countQuery = jpaQueryFactory.selectFrom(runnerPost) + .where(reviewStatusEq(reviewStatus)); if (tagReducedName != null) { countQuery.leftJoin(runnerPost.runnerPostTags.runnerPostTags, runnerPostTag) diff --git a/backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java b/backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java index 0097ee79e..a517d4a29 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/query/repository/RankQuerydslRepositoryTest.java @@ -37,4 +37,25 @@ void findMostReviewSupporterByCount() { () -> assertThat(actual.get(1).getReviewCount().getValue()).isEqualTo(4) ); } + + @DisplayName("Supporter의 count가 안되더라도 ReviewCount가 0이면 조회되지 않는다.") + @Test + void findMostReviewSupporterByCount_having_count_is_zero() { + // given + persistSupporter(new ReviewCount(0), MemberFixture.createEthan()); + persistSupporter(new ReviewCount(3), MemberFixture.createHyena()); + persistSupporter(new ReviewCount(8), MemberFixture.createDitoo()); + + // when + final int count = 3; + final int zeroCount = 1; + final List actual = rankQueryDslRepository.findMostReviewSupporterByCount(count); + + // then + assertAll( + () -> assertThat(actual).hasSize(count - zeroCount), + () -> assertThat(actual.get(0).getReviewCount().getValue()).isEqualTo(8), + () -> assertThat(actual.get(1).getReviewCount().getValue()).isEqualTo(3) + ); + } } diff --git a/backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java index 306518e61..cf2fee683 100644 --- a/backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/member/query/service/RankQueryServiceTest.java @@ -30,7 +30,6 @@ void setUp() { @Test void readMostReviewSupporter() { // given - for (int reviewedCount = 0; reviewedCount < 5; reviewedCount++) { persistSupporter(new ReviewCount(reviewedCount), MemberFixture.create(memberName("member" + reviewedCount))); } @@ -73,6 +72,48 @@ void readMostReviewSupporter() { ); } + @DisplayName("Supporter의 ReviewCount가 높은 순으로 count 개수 만큼 조회하고, 그 중 ReviewCount가 0이면 제외된다.") + @Test + void readMostReviewSupporter_when_has_zero_reviewCount() { + // given + final TechnicalTag javaTag = technicalTagQueryRepository.save(TechnicalTagFixture.createJava()); + final TechnicalTag springTag = technicalTagQueryRepository.save(TechnicalTagFixture.createSpring()); + final TechnicalTag reactTag = technicalTagQueryRepository.save(TechnicalTagFixture.createReact()); + + final Member fourthRankMember = persistMember(MemberFixture.create(memberName("fourth"))); + final List fourthRankMembersTechnicalTags = List.of(javaTag, springTag); + persistSupporter(new ReviewCount(12), fourthRankMember, fourthRankMembersTechnicalTags); + + final Member thirdRankMember = persistMember(MemberFixture.create(memberName("third"))); + final List thirdRankMembersTechnicalTags = List.of(javaTag, reactTag); + persistSupporter(new ReviewCount(13), thirdRankMember, thirdRankMembersTechnicalTags); + + final Member firstRankMember = persistMember(MemberFixture.create(memberName("first"))); + final List firstRankMembersTechnicalTags = List.of(javaTag, springTag); + persistSupporter(new ReviewCount(15), firstRankMember, firstRankMembersTechnicalTags); + + final Member secondRankMember = persistMember(MemberFixture.create(memberName("second"))); + final List secondRankMembersTechnicalTags = List.of(javaTag); + persistSupporter(new ReviewCount(14), secondRankMember, secondRankMembersTechnicalTags); + + final Member fifthRankMember = persistMember(MemberFixture.create(memberName("fifth"))); + final List fifthMembersTechnicalTags = List.of(reactTag); + persistSupporter(new ReviewCount(0), fifthRankMember, fifthMembersTechnicalTags); + + // when + final int maxCount = 5; + final RankResponses actual = rankQueryService.readMostReviewSupporter(maxCount); + + // then + assertAll( + () -> assertThat(actual.data()).hasSize(4), + () -> assertResponse(actual.data().get(0), 1, "first", 15), + () -> assertResponse(actual.data().get(1), 2, "second", 14), + () -> assertResponse(actual.data().get(2), 3, "third", 13), + () -> assertResponse(actual.data().get(3), 4, "fourth", 12) + ); + } + private static void assertResponse(final RankResponses.SupporterRank response, final int rank, final String memberName, final int reviewCount) { assertAll( () -> assertThat(response.rank()).isEqualTo(rank), diff --git a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java index d291336ed..a848a283a 100644 --- a/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java +++ b/backend/baton/src/test/java/touch/baton/domain/runnerpost/query/service/RunnerPostQueryServiceTest.java @@ -77,7 +77,7 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_firstPage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); - final int totalJavaTagPost = 3; + final int totalJavaTagAndNotStartedPost = 2; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -124,7 +124,7 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_firstPage() { List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), pageParams, - totalJavaTagPost + totalJavaTagAndNotStartedPost ); // then @@ -140,7 +140,7 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_middlePage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); - final int totalJavaTagPost = 4; + final int totalJavaTagAndNotStartedPost = 3; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -194,7 +194,7 @@ void readRunnerPostByPageInfoAndTagNameAndReviewStatus_middlePage() { List.of(RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), pageParams, - totalJavaTagPost + totalJavaTagAndNotStartedPost ); // then @@ -210,7 +210,7 @@ void readRunnerPostByPageInfoAndReviewStatus_firstPage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); - final int totalPost = 4; + final int totalNotStartedPost = 3; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -260,7 +260,7 @@ void readRunnerPostByPageInfoAndReviewStatus_firstPage() { RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), pageParams, - totalPost + totalNotStartedPost ); // then @@ -276,7 +276,7 @@ void readRunnerPostByPageInfoAndReviewStatus_middlePage() { final Tag javaTag = tagCommandRepository.save(TagFixture.create(tagName("자바"))); final Tag springTag = tagCommandRepository.save(TagFixture.create(tagName("스프링"))); - final int totalPost = 5; + final int notStartedPost = 4; final RunnerPost expectedRunnerPostOne = runnerPostQueryRepository.save(RunnerPostFixture.create( hyenaRunner, @@ -332,7 +332,7 @@ void readRunnerPostByPageInfoAndReviewStatus_middlePage() { RunnerPostResponse.Simple.of(expectedRunnerPostTwo, 0L, runnerPostTags), RunnerPostResponse.Simple.of(expectedRunnerPostOne, 0L, runnerPostTags)), pageParams, - totalPost + notStartedPost ); // then