From a121d6434097f10543bdbe2c85b28566ecb5ac4b Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Wed, 7 Feb 2024 13:38:18 +0900 Subject: [PATCH 01/14] :sparkles: Setting up CI/CD Environment (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: SonarCloud 연동 --- .github/workflows/sonarcloud.yml | 53 ++++++++++++++++++++++++++++++++ build.gradle | 11 +++++++ 2 files changed, 64 insertions(+) create mode 100644 .github/workflows/sonarcloud.yml diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml new file mode 100644 index 00000000..a8caba0c --- /dev/null +++ b/.github/workflows/sonarcloud.yml @@ -0,0 +1,53 @@ +name: SonarCloud +on: + push: + branches: + - main + - dev + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'zulu' # Alternative distribution options are available + + - name: Start MySQL container + uses: mirromutth/mysql-action@v1.1 + with: + host port: 3306 + container port: 3306 + mysql database: 'new_morandi' + mysql root password: 1234 + mysql options: '--skip-ssl' # SSL 비활성화 + + - name: Create application-dev.yml from GitHub Secret + run: | + mkdir -p src/main/resources + echo "${{ secrets.TEST_APPLICATION_YML }}" > src/main/resources/application.yml + + - name: Cache SonarCloud packages + uses: actions/cache@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + restore-keys: ${{ runner.os }}-gradle + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: ./gradlew build sonar --info diff --git a/build.gradle b/build.gradle index b79c72ed..c902c7ae 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,17 @@ plugins { id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.1.4' } +plugins { + id "org.sonarqube" version "4.4.1.3373" +} + +sonar { + properties { + property "sonar.projectKey", "SWM-Morandi_NewMorandi-Backend" + property "sonar.organization", "swm-morandi" + property "sonar.host.url", "https://sonarcloud.io" + } +} group = 'kr.co.morandi' version = '0.0.1-SNAPSHOT' From b7373ee4a267711524126b46f95196ff1df203a6 Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Wed, 7 Feb 2024 18:14:52 +0900 Subject: [PATCH 02/14] Setting up CI/CD Environment (Dev Server) (#11) * :rocket: CI/CD pipeline configuration * :rocket: Fix github actions syntax * :rocket: Initialize resource folder * :rocket: Create Dockerfile * :rocket: Fix docker image tag options * :bug: Fix docker image name * :rocket: Fix image tag options * :rocket: Fix ssh command * :rocket: Split into ci and cd jobs --- .github/workflows/cicd-dev.yml | 80 ++++++++++++++++++++++++++++++++++ Dockerfile | 4 ++ 2 files changed, 84 insertions(+) create mode 100644 .github/workflows/cicd-dev.yml create mode 100644 Dockerfile diff --git a/.github/workflows/cicd-dev.yml b/.github/workflows/cicd-dev.yml new file mode 100644 index 00000000..285da1a7 --- /dev/null +++ b/.github/workflows/cicd-dev.yml @@ -0,0 +1,80 @@ +name: cicd-dev + +on: + push: + branches: [ "dev" ] + +env: + AWS_REGION: ap-northeast-2 + ECR_REPOSITORY: dev_morandi_backend + ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-2.amazonaws.com/dev_morandi_backend + EC2_BASTION_HOST: ${{ secrets.RELEASE_BASTION_HOST }} + EC2_BACKEND_HOST: ${{ secrets.DEV_BACKEND_HOST }} # EC2 인스턴스의 Private IP + GITHUB_SHA: ${{ github.sha }} + +permissions: + contents: read + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Gradle 빌드를 추가합니다. + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '17' + + - name: Cleanup application.yml + run: rm -f src/main/resources/application.yml + + # GitHub Secret에서 application-prod.yml 내용을 불러와 파일로 저장 + - name: Create application-dev.yml from GitHub Secret + run: | + mkdir -p src/main/resources + echo "${{ secrets.DEV_APPLICATION_YML }}" > src/main/resources/application.yml + + + - name: Build with Gradle + env: + ORG_GRADLE_OPTS: "-Duser.timezone=Asia/Seoul" + run: ./gradlew clean bootJar -x test + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.ECR_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.ECR_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + mask-aws-account-id: true + + - name: Login to Private ECR + run: aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin ${{ env.ECR_REGISTRY }} + + - name: Build Docker Image + run: docker build -t ${{ env.ECR_REGISTRY }}:${{ github.sha }} . + + - name: Push Docker Image to ECR + run: docker push ${{ env.ECR_REGISTRY }}:${{ github.sha }} + + deploy: + name: Deploy to EC2 + needs: build-and-push + runs-on: ubuntu-latest + steps: + - name: appleboy SSH and Deploy to EC2 + uses: appleboy/ssh-action@master # ssh 접속하는 오픈소스 + with: + host: ${{ env.EC2_BASTION_HOST }} + debug: true + key: ${{ secrets.BASTION_SSH_SECRET_KEY }} + username: ubuntu + port: 22 + envs: EC2_BACKEND_HOST,GITHUB_SHA,ECR_REGISTRY + script: | + ssh -o StrictHostKeyChecking=no ubuntu@$EC2_BACKEND_HOST "export TAG=$GITHUB_SHA && bash /home/ubuntu/morandi-backend/deploy.sh" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f255b205 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17-oracle +WORKDIR /app +COPY build/libs/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file From b5ca1d73d068c6ea94c7bc092ec2e89b1abd9af8 Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Sat, 24 Feb 2024 16:30:40 +0900 Subject: [PATCH 03/14] =?UTF-8?q?Feat/#15=E2=9C=A8=20ContentType=20DB=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: Create ContentType database table * :sparkles: Compose contentType database table * :sparkles: Setting up CI/CD Environment (#2) * :sparkles: SonarCloud 연동 * Setting up CI/CD Environment (Dev Server) (#11) * :rocket: CI/CD pipeline configuration * :rocket: Fix github actions syntax * :rocket: Initialize resource folder * :rocket: Create Dockerfile * :rocket: Fix docker image tag options * :bug: Fix docker image name * :rocket: Fix image tag options * :rocket: Fix ssh command * :rocket: Split into ci and cd jobs * :sparkles: ContentType DB 매핑 추가, 출제 기준 테스트코드 작성 * :sparkles: ContentType DB 매핑 및 테스트코드 작성 RandomCriteria 유효성 검증 테스트 추가 * :art: 테스트 범위가 중복되어 분리 * :pencil2: BookMark에 Builder 추가 * :fire: 사용하지 않는 import 제거 * :white_check_mark: Problem activate메서드 테스트 작성 * :heavy_plus_sign: Test시 h2사용하도록 의존성 추가 * :art: Difficulty 필드를 enum DefenseTier로 변경 * :sparkles: CustomDefenseProblems, DailyTestProblems에서 problem과 ManyToOne 연관관계 추가 * :white_check_mark: 커스텀 디펜스 테스트코드 | Member 수정 및 테스트 코드 작성 * :white_check_mark: 커스텀 디펜스 문제 수, 설정 시간 테스트코드 * :pencil2: 오타 주석 제거 * :art: If문에 공백 추가 * :memo: ProblemStatus 주석 및 메세지를 추가 * :sparkles: 같은 난이도를 가지는 범위로 DifficultyRange를 설정할 수 있다. * :recycle: Random Defense 패키지 구조 변경 * :art: CustomDefense problemCount 래퍼타입으로 변경 --------- Co-authored-by: aj4941@naver.com --- build.gradle | 10 +- .../backend/NewMorandiApplication.java | 2 - .../backend/config/JpaAuditingConfig.java | 8 + .../co/morandi/backend/domain/BaseEntity.java | 27 +++ .../backend/domain/algorithm/Algorithm.java | 24 +++ .../domain/algorithm/AlgorithmRepository.java | 6 + .../backend/domain/bookmark/BookMark.java | 27 +++ .../domain/bookmark/BookMarkRepository.java | 6 + .../ContentMemberLikes.java | 28 +++ .../ContentMemberLikesRepository.java | 6 + .../domain/contenttype/ContentType.java | 27 +++ .../customdefense/CustomDefense.java | 78 ++++++++ .../customdefense/CustomDefenseProblems.java | 48 +++++ .../CustomDefenseProblemsRepository.java | 6 + .../CustomDefenseRepository.java | 9 + .../customdefense/DefenseTier.java | 16 ++ .../contenttype/customdefense/Visibility.java | 13 ++ .../contenttype/dailytest/DailyTest.java | 21 ++ .../dailytest/DailyTestProblems.java | 33 ++++ .../DailyTestProblemsRepository.java | 6 + .../dailytest/DailyTestRepository.java | 7 + .../random/randomcriteria/RandomCriteria.java | 68 +++++++ .../random/randomdefense/RandomDefense.java | 24 +++ .../RandomDefenseRepository.java | 6 + .../RandomStageDefense.java | 23 +++ .../RandomStageDefenseRepository.java | 6 + .../domain/contenttype/tier/ProblemTier.java | 18 ++ .../morandi/backend/domain/member/Member.java | 49 +++++ .../domain/member/MemberRepository.java | 8 + .../backend/domain/member/SocialType.java | 14 ++ .../backend/domain/problem/Problem.java | 49 +++++ .../domain/problem/ProblemRepository.java | 11 ++ .../backend/domain/problem/ProblemStatus.java | 16 ++ .../problemalgorithm/ProblemAlgorithm.java | 28 +++ .../ProblemAlgorithmRepository.java | 6 + .../CustomDefenseRepositoryTest.java | 82 ++++++++ .../customdefense/CustomDefenseTest.java | 180 ++++++++++++++++++ .../randomcriteria/DifficultyRangeTest.java | 59 ++++++ .../randomcriteria/RandomCriteriaTest.java | 57 ++++++ .../RandomDefenseRepositoryTest.java | 96 ++++++++++ .../domain/member/MemberRepositoryTest.java | 69 +++++++ .../backend/domain/member/MemberTest.java | 32 ++++ .../domain/problem/ProblemRepositoryTest.java | 53 ++++++ .../backend/domain/problem/ProblemTest.java | 44 +++++ 44 files changed, 1403 insertions(+), 3 deletions(-) create mode 100644 src/main/java/kr/co/morandi/backend/config/JpaAuditingConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/BaseEntity.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/bookmark/BookMarkRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikesRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/DefenseTier.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/Visibility.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/tier/ProblemTier.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/member/Member.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/member/MemberRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/member/SocialType.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/problem/Problem.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/problem/ProblemStatus.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithm.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithmRepository.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepositoryTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/DifficultyRangeTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/RandomCriteriaTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/member/MemberRepositoryTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java diff --git a/build.gradle b/build.gradle index c902c7ae..750559cc 100644 --- a/build.gradle +++ b/build.gradle @@ -34,15 +34,23 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' + runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'com.h2database:h2' } tasks.named('test') { diff --git a/src/main/java/kr/co/morandi/backend/NewMorandiApplication.java b/src/main/java/kr/co/morandi/backend/NewMorandiApplication.java index 1dec55a6..a0d77b46 100644 --- a/src/main/java/kr/co/morandi/backend/NewMorandiApplication.java +++ b/src/main/java/kr/co/morandi/backend/NewMorandiApplication.java @@ -5,9 +5,7 @@ @SpringBootApplication public class NewMorandiApplication { - public static void main(String[] args) { SpringApplication.run(NewMorandiApplication.class, args); } - } diff --git a/src/main/java/kr/co/morandi/backend/config/JpaAuditingConfig.java b/src/main/java/kr/co/morandi/backend/config/JpaAuditingConfig.java new file mode 100644 index 00000000..32bccb4c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/config/JpaAuditingConfig.java @@ -0,0 +1,8 @@ +package kr.co.morandi.backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@Configuration +@EnableJpaAuditing +public class JpaAuditingConfig { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/BaseEntity.java b/src/main/java/kr/co/morandi/backend/domain/BaseEntity.java new file mode 100644 index 00000000..9b9ee285 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/BaseEntity.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.domain; + +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@MappedSuperclass +@Getter +@SuperBuilder +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class BaseEntity { + @CreatedDate + private LocalDateTime createdDateTime; + + @LastModifiedDate + private LocalDateTime modifiedDateTime; + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java b/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java new file mode 100644 index 00000000..355fa914 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java @@ -0,0 +1,24 @@ +package kr.co.morandi.backend.domain.algorithm; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import kr.co.morandi.backend.domain.BaseEntity; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Algorithm extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long algorithmId; + + private String algorithmName; + + @Builder + private Algorithm(String algorithmName) { + this.algorithmName = algorithmName; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java new file mode 100644 index 00000000..f8622dd1 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.algorithm; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AlgorithmRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java b/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java new file mode 100644 index 00000000..44063a42 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.domain.bookmark; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor +public class BookMark extends BaseEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long bookMarkId; + + @ManyToOne(fetch = FetchType.LAZY) + private ContentType contentType; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @Builder + private BookMark(ContentType contentType, Member member) { + this.contentType = contentType; + this.member = member; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMarkRepository.java b/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMarkRepository.java new file mode 100644 index 00000000..5bc17acd --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMarkRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.bookmark; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BookMarkRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java new file mode 100644 index 00000000..54def21f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java @@ -0,0 +1,28 @@ +package kr.co.morandi.backend.domain.contentmemberlikes; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Table +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ContentMemberLikes extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long memberLikesId; + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + @ManyToOne(fetch = FetchType.LAZY) + private ContentType contentType; + + @Builder + private ContentMemberLikes(Member member, ContentType contentType) { + this.member = member; + this.contentType = contentType; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikesRepository.java b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikesRepository.java new file mode 100644 index 00000000..377decc3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikesRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.contentmemberlikes; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ContentMemberLikesRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java new file mode 100644 index 00000000..4138b299 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.domain.contenttype; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class ContentType extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long contentTypeId; + + private String contentName; + + private Long attemptCount; + + public ContentType(String contentName) { + this.contentName = contentName; + this.attemptCount = 0L; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java new file mode 100644 index 00000000..80469aca --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java @@ -0,0 +1,78 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Entity +@DiscriminatorValue("CustomDefense") +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CustomDefense extends ContentType { + + private LocalDateTime createDate; + + private Integer problemCount; + + private String description; + + @Enumerated(EnumType.STRING) + private Visibility visibility; + + @Enumerated(EnumType.STRING) + private DefenseTier defenseTier; + + private Long timeLimit; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @Builder.Default + @OneToMany(mappedBy = "customDefense", cascade = CascadeType.ALL) + private List customDefenseProblems = new ArrayList<>(); + + private CustomDefense(List problems, Member member, String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { + super(contentName); + this.problemCount = isValidProblemCount(problems.size()); + this.timeLimit = isValidTimeLimit(timeLimit); + this.description = description; + this.visibility = visibility; + this.defenseTier = defenseTier; + this.member = member; + this.customDefenseProblems = problems.stream() + .map(problem -> new CustomDefenseProblems(this, problem)) + .collect(Collectors.toList()); + this.createDate = createDate; + } + + public static CustomDefense create(List problems, Member member,String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { + return new CustomDefense(problems, member, contentName, description, visibility, defenseTier, timeLimit, createDate); + } + + private Long isValidTimeLimit(Long timeLimit) { + if (timeLimit < 0) { + throw new IllegalArgumentException("커스텀 디펜스 제한 시간은 0보다 커야 합니다."); + } + return timeLimit; + } + + private int isValidProblemCount(int problemCount) { + if (problemCount < 1) { + throw new IllegalArgumentException("커스텀 디펜스에는 최소 한 개의 문제가 포함되어야 합니다."); + } + return problemCount; + } + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java new file mode 100644 index 00000000..91b1a7ff --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java @@ -0,0 +1,48 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CustomDefenseProblems extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long customProblemsId; + + @ManyToOne(fetch = FetchType.LAZY) + private CustomDefense customDefense; + + @ManyToOne(fetch = FetchType.LAZY) + private Problem problem; + private Long submitCount; + + private Long solvedCount; + + public CustomDefenseProblems(CustomDefense customDefense, Problem problem) { + this.customDefense = customDefense; + this.problem = problem; + this.submitCount = 0L; + this.solvedCount = 0L; + } + + @Builder + private CustomDefenseProblems(CustomDefense customDefense, Problem problem, Long submitCount, Long solvedCount) { + this.customDefense = customDefense; + this.problem = problem; + this.submitCount = submitCount; + this.solvedCount = solvedCount; + } + + public static CustomDefenseProblems create(CustomDefense customDefense, Problem problem) { + return CustomDefenseProblems.builder() + .submitCount(0L) + .solvedCount(0L) + .customDefense(customDefense) + .problem(problem) + .build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java new file mode 100644 index 00000000..349be994 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDefenseProblemsRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepository.java new file mode 100644 index 00000000..eadcd8e2 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepository.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CustomDefenseRepository extends JpaRepository { + List findAllByVisibility(Visibility visibility); +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/DefenseTier.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/DefenseTier.java new file mode 100644 index 00000000..0c5030c6 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/DefenseTier.java @@ -0,0 +1,16 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum DefenseTier { + BRONZE(1), + SILVER(2), + GOLD(3), + PLATINUM(4), + DIAMOND(5), + RUBY(6); + private final int tier; +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/Visibility.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/Visibility.java new file mode 100644 index 00000000..7e80c06d --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/Visibility.java @@ -0,0 +1,13 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Visibility { + OPEN(true), + CLOSE(false); + + private final boolean isVisible; +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java new file mode 100644 index 00000000..61da9b99 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java @@ -0,0 +1,21 @@ +package kr.co.morandi.backend.domain.contenttype.dailytest; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@DiscriminatorValue("DailyTest") +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class DailyTest extends ContentType { + + private LocalDateTime date; + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java new file mode 100644 index 00000000..e310c92b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.domain.contenttype.dailytest; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DailyTestProblems extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long dailyProblemsId; + + @ManyToOne(fetch = FetchType.LAZY) + private DailyTest dailyTest; + + @ManyToOne(fetch = FetchType.LAZY) + private Problem problem; + + private Long submitCount; + + private Long solvedCount; + + @Builder + private DailyTestProblems(DailyTest dailyTest, Problem problem, Long submitCount, Long solvedCount) { + this.dailyTest = dailyTest; + this.problem = problem; + this.submitCount = submitCount; + this.solvedCount = solvedCount; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java new file mode 100644 index 00000000..3b96eee0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.contenttype.dailytest; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DailyTestProblemsRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java new file mode 100644 index 00000000..6f896bca --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.domain.contenttype.dailytest; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DailyTestRepository extends JpaRepository { + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java new file mode 100644 index 00000000..85fa1f11 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java @@ -0,0 +1,68 @@ +package kr.co.morandi.backend.domain.contenttype.random.randomcriteria; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RandomCriteria { + @Embedded + private DifficultyRange difficultyRange; + private Long minSolvedCount; + private Long maxSolvedCount; + + @Builder + private RandomCriteria(DifficultyRange difficultyRange, Long minSolvedCount, Long maxSolvedCount) { + this.difficultyRange = difficultyRange; + this.minSolvedCount = minSolvedCount; + this.maxSolvedCount = maxSolvedCount; + } + + public static RandomCriteria of(DifficultyRange difficultyRange, Long minSolvedCount, Long maxSolvedCount) { + if (minSolvedCount == null || maxSolvedCount == null) + throw new IllegalArgumentException("Solved count must not be null"); + if (minSolvedCount < 0 || maxSolvedCount < 0) + throw new IllegalArgumentException("Solved count must be greater than or equal to 0"); + if (minSolvedCount >= maxSolvedCount) + throw new IllegalArgumentException("Min solved count must be less than or equal to max solved count"); + + + return RandomCriteria.builder() + .difficultyRange(difficultyRange) + .minSolvedCount(minSolvedCount) + .maxSolvedCount(maxSolvedCount) + .build(); + } + + @Embeddable + @Getter + @NoArgsConstructor(access = AccessLevel.PROTECTED) + public static class DifficultyRange { + private ProblemTier startDifficulty; + private ProblemTier endDifficulty; + + @Builder + private DifficultyRange(ProblemTier startDifficulty, ProblemTier endDifficulty) { + this.startDifficulty = startDifficulty; + this.endDifficulty = endDifficulty; + } + + public static DifficultyRange of(ProblemTier startDifficulty, ProblemTier endDifficulty) { + if (startDifficulty == null || endDifficulty == null) + throw new IllegalArgumentException("DifficultyRange must not be null"); + if (startDifficulty.compareTo(endDifficulty) > 0) + throw new IllegalArgumentException("Start difficulty must be less than or equal to end difficulty"); + + return DifficultyRange.builder() + .startDifficulty(startDifficulty) + .endDifficulty(endDifficulty) + .build(); + } + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java new file mode 100644 index 00000000..1da0bb5d --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java @@ -0,0 +1,24 @@ +package kr.co.morandi.backend.domain.contenttype.random.randomdefense; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Entity +@DiscriminatorValue("RandomDefense") +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RandomDefense extends ContentType { + + @Embedded + private RandomCriteria randomCriteria; + + private Long problemCount; + + private Long timeLimit; + + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseRepository.java new file mode 100644 index 00000000..1ee74496 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.contenttype.random.randomdefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RandomDefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java new file mode 100644 index 00000000..059c21be --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java @@ -0,0 +1,23 @@ +package kr.co.morandi.backend.domain.contenttype.random.randomstagedefense; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import lombok.*; +import lombok.experimental.SuperBuilder; + +@Entity +@DiscriminatorValue("StageDefense") +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RandomStageDefense extends ContentType { + + @Embedded + private RandomCriteria randomCriteria; + + private Double averageStage; + + private Long timeLimit; + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java new file mode 100644 index 00000000..986a0db1 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.contenttype.random.randomstagedefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RandomStageDefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/tier/ProblemTier.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/tier/ProblemTier.java new file mode 100644 index 00000000..0a9d5c46 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/tier/ProblemTier.java @@ -0,0 +1,18 @@ +package kr.co.morandi.backend.domain.contenttype.tier; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ProblemTier { + UNRANKED(0), + B5(1),B4(2),B3(3),B2(4),B1(5), + S5(6),S4(7),S3(8),S2(9),S1(10), + G5(11),G4(12),G3(13),G2(14),G1(15), + P5(16),P4(17),P3(18),P2(19),P1(20), + D5(21),D4(22),D3(23),D2(24),D1(25), + R5(26),R4(27),R3(28),R2(29),R1(30); + + private final int tier; +} diff --git a/src/main/java/kr/co/morandi/backend/domain/member/Member.java b/src/main/java/kr/co/morandi/backend/domain/member/Member.java new file mode 100644 index 00000000..97428669 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/member/Member.java @@ -0,0 +1,49 @@ +package kr.co.morandi.backend.domain.member; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long memberId; + + @Column(unique = true) + private String nickname; + + private String baekjoonId; + + private String email; + + @Enumerated(EnumType.STRING) + private SocialType socialType; + + private String profileImageURL; + + private String description; + + @Builder + private Member(Long memberId, String nickname, String baekjoonId, String email, SocialType socialType, String profileImageURL, String description) { + this.memberId = memberId; + this.nickname = nickname; + this.baekjoonId = baekjoonId; + this.email = email; + this.socialType = socialType; + this.profileImageURL = profileImageURL; + this.description = description; + } + + public static Member create(String nickname, String email, SocialType socialType, String profileImageURL, String description) { + return Member.builder() + .nickname(nickname) + .email(email) + .socialType(socialType) + .profileImageURL(profileImageURL) + .description(description) + .build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/member/MemberRepository.java b/src/main/java/kr/co/morandi/backend/domain/member/MemberRepository.java new file mode 100644 index 00000000..97e476e9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/member/MemberRepository.java @@ -0,0 +1,8 @@ +package kr.co.morandi.backend.domain.member; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + Boolean existsByNickname(String nickname); + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/member/SocialType.java b/src/main/java/kr/co/morandi/backend/domain/member/SocialType.java new file mode 100644 index 00000000..ebfcaefd --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/member/SocialType.java @@ -0,0 +1,14 @@ +package kr.co.morandi.backend.domain.member; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SocialType { + GOOGLE("google"), + GITHUB("github"), + NAVER("naver"); + + private final String provider; +} diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java b/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java new file mode 100644 index 00000000..384146ce --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java @@ -0,0 +1,49 @@ +package kr.co.morandi.backend.domain.problem; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import lombok.*; + +import static kr.co.morandi.backend.domain.problem.ProblemStatus.HOLD; +import static kr.co.morandi.backend.domain.problem.ProblemStatus.INIT; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Problem extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long problemId; + + private Long baekjoonProblemId; + + @Enumerated(EnumType.STRING) + private ProblemTier problemTier; + + @Enumerated(EnumType.STRING) + private ProblemStatus problemStatus; + + private Long solvedCount; + + @Builder + private Problem(Long baekjoonProblemId, ProblemTier problemTier, ProblemStatus problemStatus, Long solvedCount) { + this.baekjoonProblemId = baekjoonProblemId; + this.problemTier = problemTier; + this.problemStatus = problemStatus; + this.solvedCount = solvedCount; + } + + public void activate() { + this.problemStatus = ProblemStatus.ACTIVE; + } + + public static Problem create(Long baekjoonProblemId, ProblemTier problemTier, Long solvedCount) { + return Problem.builder() + .baekjoonProblemId(baekjoonProblemId) + .problemTier(problemTier) + .problemStatus(INIT) + .solvedCount(solvedCount) + .build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java b/src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java new file mode 100644 index 00000000..9d4e87a0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.domain.problem; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ProblemRepository extends JpaRepository { + + List findAllByProblemStatus(ProblemStatus problemStatus); +} + diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/ProblemStatus.java b/src/main/java/kr/co/morandi/backend/domain/problem/ProblemStatus.java new file mode 100644 index 00000000..3a1ea179 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/problem/ProblemStatus.java @@ -0,0 +1,16 @@ +package kr.co.morandi.backend.domain.problem; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ProblemStatus { + //배치 작업으로 문제가 자동으로 추가됐을 때, 관리자의 검토 없이 문제가 사용되는 것을 방지하기 위해 추가했습니다. + INIT("새롭게 추가되어 확인이 필요한 문제입니다."), + ACTIVE("활성화되어 사용 가능한 문제입니다."), + HOLD("홀드된 문제입니다. 문제가 활성화되기 전까지 사용할 수 없습니다."), + INACTIVE("비활성화된 문제입니다. 문제가 활성화되기 전까지 사용할 수 없습니다."); + + private final String info; +} diff --git a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithm.java b/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithm.java new file mode 100644 index 00000000..7f41690f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithm.java @@ -0,0 +1,28 @@ +package kr.co.morandi.backend.domain.problemalgorithm; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.algorithm.Algorithm; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ProblemAlgorithm extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long problemAlgorithmId; + + @ManyToOne(fetch = FetchType.LAZY) + private Algorithm algorithm; + + @ManyToOne(fetch = FetchType.LAZY) + private Problem problem; + + @Builder + private ProblemAlgorithm(Algorithm algorithm, Problem problem) { + this.algorithm = algorithm; + this.problem = problem; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithmRepository.java new file mode 100644 index 00000000..eb9f54b1 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithmRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.problemalgorithm; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProblemAlgorithmRepository extends JpaRepository { +} diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepositoryTest.java new file mode 100644 index 00000000..def61f60 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepositoryTest.java @@ -0,0 +1,82 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.member.MemberRepository; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.*; +import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.CLOSE; +import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class CustomDefenseRepositoryTest { + + @Autowired + private CustomDefenseRepository customDefenseRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CustomDefenseProblemsRepository customDefenseProblemsRepository; + @AfterEach + void tearDown() { + customDefenseProblemsRepository.deleteAllInBatch(); + customDefenseRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + memberRepository.deleteAllInBatch(); + } + + @DisplayName("공개 상태의 커스텀 디펜스를 조회할 수 있다.") + @Test + void findAllByVisibility() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + memberRepository.save(member); + + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + problemRepository.saveAll(List.of(problem1, problem2, problem3)); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + CustomDefense customDefense1 = CustomDefense.create(List.of(problem1,problem2), member, "커스텀 디펜스1","커스텀 디펜스1 설명", OPEN, BRONZE, 60L, now); + CustomDefense customDefense2 = CustomDefense.create(List.of(problem1,problem3), member, "커스텀 디펜스2","커스텀 디펜스2 설명", OPEN, SILVER, 60L, now); + CustomDefense customDefense3 = CustomDefense.create(List.of(problem2,problem3), member, "커스텀 디펜스3","커스텀 디펜스3 설명", CLOSE, GOLD, 60L, now); + + customDefenseRepository.saveAll(List.of(customDefense1, customDefense2, customDefense3)); + + // when + List customDefenses = customDefenseRepository.findAllByVisibility(OPEN); + + // then + assertThat(customDefenses) + .hasSize(2) + .extracting("contentName","description","visibility") + .containsExactlyInAnyOrder( + tuple("커스텀 디펜스1","커스텀 디펜스1 설명",OPEN), + tuple("커스텀 디펜스2","커스텀 디펜스2 설명",OPEN) + ); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java new file mode 100644 index 00000000..516134b7 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java @@ -0,0 +1,180 @@ +package kr.co.morandi.backend.domain.contenttype.customdefense; + +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.member.MemberRepository; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +class CustomDefenseTest { + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private MemberRepository memberRepository; + + + @Autowired + private CustomDefenseProblemsRepository customDefenseProblemsRepository; + + @AfterEach + void tearDown() { + customDefenseProblemsRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + memberRepository.deleteAllInBatch(); + } + + @DisplayName("커스텀 디펜스를 생성하면 등록 시간을 기록한다.") + @Test + void registeredWithDateTime() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + memberRepository.save(member); + + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + List problems = List.of(problem1, problem2); + problemRepository.saveAll(problems); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + // when + CustomDefense customDefense = CustomDefense.create(problems, member, "커스텀 디펜스1", "커스텀 디펜스1 설명", OPEN, GOLD, 60L, now); + + // then + assertThat(customDefense.getCreateDate()).isEqualTo(now); + + } + + @DisplayName("커스텀 디펜스를 생성하면 컨텐츠 이름과 설명을 기록한다.") + @Test + void createCustomDefenseWithContentName() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + memberRepository.save(member); + + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + List problems = List.of(problem1, problem2); + problemRepository.saveAll(problems); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + // when + CustomDefense customDefense = CustomDefense.create(problems, member, "커스텀 디펜스1","커스텀 디펜스1 설명", OPEN, GOLD, 60L, now); + + // then + assertThat(customDefense) + .extracting("contentName", "description") + .containsExactlyInAnyOrder( + "커스텀 디펜스1", "커스텀 디펜스1 설명"); + } + + @DisplayName("커스텀 디펜스에 포함된 문제 개수를 조회할 수 있다.") + @Test + void createCustomDefenseProblemCount() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + memberRepository.save(member); + + List problems = createProblems(); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + + // when + CustomDefense customDefense = CustomDefense.create(problems, member, "커스텀 디펜스1", "커스텀 디펜스1 설명", OPEN, GOLD, 60L, now); + + // then + assertThat(customDefense.getCustomDefenseProblems()) + .hasSize(3) + .extracting("problem.baekjoonProblemId", "problem.problemTier") + .containsExactlyInAnyOrder( + tuple(1L, B5), + tuple(2L, S5), + tuple(3L, G5) + ); + } + + + @DisplayName("커스텀 디펜스를 빈 문제 리스트로 생성하면 예외가 발생한다") + @Test + void createCustomDefenseWithoutProblem() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + memberRepository.save(member); + + List problems = Collections.emptyList(); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + // when & then + assertThatThrownBy( () -> CustomDefense.create(problems, member, "커스텀 디펜스1", "커스텀 디펜스1 설명", OPEN, GOLD, 60L, now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("커스텀 디펜스에는 최소 한 개의 문제가 포함되어야 합니다."); + + } + + @DisplayName("커스텀 디펜스 제한 시간을 0으로 설정하면 예외를 발생한다.") + @Test + void createCustomDefenseWithZeroTimeLimit() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + memberRepository.save(member); + + List problems = createProblems(); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + // when & then + assertThatThrownBy( () -> CustomDefense.create(problems, member, "커스텀 디펜스1", "커스텀 디펜스1 설명", OPEN, GOLD, -1L, now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("커스텀 디펜스 제한 시간은 0보다 커야 합니다."); + } + + @DisplayName("커스텀 디펜스 제한 시간을 0 미만의 값으로 설정하면 예외를 발생한다.") + @Test + void createCustomDefenseWithNegativeTimeLimit() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + memberRepository.save(member); + + List problems = createProblems(); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + // when & then + assertThatThrownBy( () -> CustomDefense.create(problems, member, "커스텀 디펜스1", "커스텀 디펜스1 설명", OPEN, GOLD, -1L, now)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("커스텀 디펜스 제한 시간은 0보다 커야 합니다."); + + } + + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + List problems = List.of(problem1, problem2, problem3); + problemRepository.saveAll(problems); + return problems; + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/DifficultyRangeTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/DifficultyRangeTest.java new file mode 100644 index 00000000..893d0c72 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/DifficultyRangeTest.java @@ -0,0 +1,59 @@ +package kr.co.morandi.backend.domain.contenttype.randomcriteria; + +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ActiveProfiles("test") +class DifficultyRangeTest { + + @DisplayName("같은 난이도로 DifficultyRange를 생성할 수 있다.") + @Test + void startAndEndDifficultySameException() { + // given + ProblemTier start = B1; + ProblemTier end = B1; + + // when + RandomCriteria.DifficultyRange difficultyRange = RandomCriteria.DifficultyRange.of(start, end); + + // then + assertThat(difficultyRange) + .extracting("startDifficulty", "endDifficulty") + .containsExactly(start, end); + + } + + @DisplayName("최소 난이도가 최대 난이도보다 큰 값으로 DifficultyRange를 생성하려고 하면 예외가 발생한다.") + @Test + void startDifficultyGreaterThanEndDifficultyException() { + // given + ProblemTier start = B1; + ProblemTier end = B5; + + // when & then + assertThatThrownBy(() -> RandomCriteria.DifficultyRange.of(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Start difficulty must be less than or equal to end difficulty"); + } + + @DisplayName("시작 난이도나 끝 난이도가 null로 DifficultyRange를 생성하려고 하면 예외가 발생한다.") + @Test + void startOrEndDifficultyNullException() { + // given + ProblemTier start = null; + ProblemTier end = B5; + + // when & then + assertThatThrownBy(() -> RandomCriteria.DifficultyRange.of(start, end)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("DifficultyRange must not be null"); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/RandomCriteriaTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/RandomCriteriaTest.java new file mode 100644 index 00000000..adeb1e7e --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/RandomCriteriaTest.java @@ -0,0 +1,57 @@ +package kr.co.morandi.backend.domain.contenttype.randomcriteria; + +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ActiveProfiles("test") +class RandomCriteriaTest { + @DisplayName("출제 기준에서 최소 푼 사람 수와 최대 푼 사람 수이 0보다 작으면 예외가 발생한다.") + @Test + void minAndMaxSolvedCountZeroOrMoreException() { + // given + long minSolvedCount = -2L; + long maxSolvedCount = -1L; + RandomCriteria.DifficultyRange difficultyRange = RandomCriteria.DifficultyRange.of(ProblemTier.B5, ProblemTier.B1); + + + // when & then + assertThatThrownBy(() -> RandomCriteria.of(difficultyRange, minSolvedCount, maxSolvedCount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Solved count must be greater than or equal to 0"); + } + + @DisplayName("최소 푼 사람 수가 최대 푼 사람 수보다 크면 예외가 발생한다.") + @Test + void minSolvedCountLessThanMaxSolvedCountException() { + // given + long minSolvedCount = 100L; + long maxSolvedCount = 50L; + RandomCriteria.DifficultyRange difficultyRange = RandomCriteria.DifficultyRange.of(ProblemTier.B5, ProblemTier.B1); + + // when & then + assertThatThrownBy(() -> RandomCriteria.of(difficultyRange, minSolvedCount, maxSolvedCount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Min solved count must be less than or equal to max solved count"); + + } + + @DisplayName("푼 사람 수의 범위가 같으면 예외가 발생한다.") + @Test + void minAndMaxSolvedCountSameException() { + // given + long minSolvedCount = 100L; + long maxSolvedCount = 100L; + RandomCriteria.DifficultyRange difficultyRange = RandomCriteria.DifficultyRange.of(ProblemTier.B5, ProblemTier.B1); + + // when & then + assertThatThrownBy(() -> RandomCriteria.of(difficultyRange, minSolvedCount, maxSolvedCount)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Min solved count must be less than or equal to max solved count"); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java new file mode 100644 index 00000000..ad2cba87 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java @@ -0,0 +1,96 @@ +package kr.co.morandi.backend.domain.contenttype.randomdefense; + + +import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; +import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefenseRepository; +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class RandomDefenseRepositoryTest { + + @Autowired + private RandomDefenseRepository randomDefenseRepository; + + @AfterEach + void tearDown() { + randomDefenseRepository.deleteAllInBatch(); + } + + @Test + @DisplayName("저장된 랜덤 디펜스의 정보를 조회할 수 있다.") + void findByContentName() { + // given + List randomDefenses = createRandomDefense(); + randomDefenseRepository.saveAll(randomDefenses); + + // when + RandomDefense findRandomDefense = randomDefenseRepository.findById(randomDefenses.get(0).getContentTypeId()).get(); + + // then + assertThat(findRandomDefense) + .extracting("randomCriteria.maxSolvedCount", "randomCriteria.minSolvedCount", "timeLimit", "problemCount", "randomCriteria.difficultyRange.startDifficulty", "randomCriteria.difficultyRange.endDifficulty") + .containsExactly(200L, 100L, 1000L, 4L, B5, B1); + } + + @DisplayName("랜덤 디펜스들을 모두 조회하여 가져올 수 있다.") + @Test + void findAllRandomDefense(){ + // given + List randomDefenses = createRandomDefense(); + randomDefenseRepository.saveAll(randomDefenses); + + // when + List findRandomDefenses = randomDefenseRepository.findAll(); + + // then + assertThat(findRandomDefenses).hasSize(3) + .extracting("randomCriteria.maxSolvedCount", "randomCriteria.minSolvedCount", "timeLimit", "problemCount", "randomCriteria.difficultyRange.startDifficulty", "randomCriteria.difficultyRange.endDifficulty") + .containsExactlyInAnyOrder( + tuple(200L, 100L, 1000L, 4L, B5, B1), + tuple(200L, 100L, 1000L, 4L, S5, S1), + tuple(200L, 100L, 1000L, 4L, G5, G1) + ); + } + + private List createRandomDefense() { + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria.DifficultyRange silverRange = RandomCriteria.DifficultyRange.of(S5, S1); + RandomCriteria.DifficultyRange goldRange = RandomCriteria.DifficultyRange.of(G5, G1); + + RandomDefense bronzeDefense = RandomDefense.builder() + .timeLimit(1000L) + .problemCount(4L) + .contentName("브론즈 랜덤 디펜스") + .randomCriteria(RandomCriteria.of(bronzeRange,100L,200L)) + .build(); + + RandomDefense silverDefense = RandomDefense.builder() + .timeLimit(1000L) + .problemCount(4L) + .contentName("실버 랜덤 디펜스") + .randomCriteria(RandomCriteria.of(silverRange,100L,200L)) + .build(); + + RandomDefense goldDefense = RandomDefense.builder() + .timeLimit(1000L) + .problemCount(4L) + .contentName("골드 랜덤 디펜스") + .randomCriteria(RandomCriteria.of(goldRange,100L,200L)) + .build(); + + return List.of(bronzeDefense, silverDefense, goldDefense); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/member/MemberRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/member/MemberRepositoryTest.java new file mode 100644 index 00000000..7cc5c494 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/member/MemberRepositoryTest.java @@ -0,0 +1,69 @@ +package kr.co.morandi.backend.domain.member; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.test.context.ActiveProfiles; + +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@ActiveProfiles("test") +class MemberRepositoryTest { + + @Autowired + private MemberRepository memberRepository; + + @AfterEach + void tearDown() { + memberRepository.deleteAllInBatch(); + } + + @DisplayName("닉네임이 이미 존재하면 true를 반환한다") + @Test + void existsByNickname() { + // given + String nickname = "test"; + Member member = Member.create(nickname, "test@test.com", GOOGLE, "testImageUrl", "testDescription"); + memberRepository.save(member); + + // when + Boolean result = memberRepository.existsByNickname(nickname); + + // then + assertThat(result).isTrue(); + } + @DisplayName("닉네임이 이미 존재하지 않으면 false를 반환한다") + @Test + void whenNicknameNotExists() { + // given + String nickname = "test"; + + // when + Boolean result = memberRepository.existsByNickname(nickname); + + // then + assertThat(result).isFalse(); + } + + @DisplayName("닉네임이 이미 있는데 다른 회원이 사용하려고 하면 예외를 발생시킨다") + @Test + void test() { + // given + String nickname = "test"; + Member originMember = Member.create(nickname, "test@test.com", GOOGLE, "testImageUrl", "testDescription"); + memberRepository.save(originMember); + + Member newMember = Member.create(nickname, "test2@test.com", GOOGLE, "testImageUrl", "testDescription"); + // when & then + assertThatThrownBy(() -> memberRepository.save(newMember)) + .isInstanceOf(DataIntegrityViolationException.class); + + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java b/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java new file mode 100644 index 00000000..841ff368 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java @@ -0,0 +1,32 @@ +package kr.co.morandi.backend.domain.member; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; + +class MemberTest { + + @DisplayName("회원을 생성한다") + @Test + void createMember() { + // given + String nickname = "test"; + String email = "test@test.com"; + SocialType socialType = GOOGLE; + String profileImageURL = "testImageUrl"; + String description = "testDescription"; + + // when + Member member = Member.create(nickname, email, socialType, profileImageURL, description); + + // then + assertThat(member) + .extracting("nickname", "email", "socialType", "profileImageURL", "description") + .containsExactly(nickname, email, socialType, profileImageURL, description); + } + + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java new file mode 100644 index 00000000..b3692ccf --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java @@ -0,0 +1,53 @@ +package kr.co.morandi.backend.domain.problem; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class ProblemRepositoryTest { + + @Autowired + private ProblemRepository problemRepository; + + @AfterEach + void tearDown() { + problemRepository.deleteAllInBatch(); + } + + @DisplayName("활성화된 문제들의 리스트를 조회할 수 있다.") + @Test + void findAllByProblemStatus() { + // given + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + problem1.activate(); + problem2.activate(); + problemRepository.saveAll(List.of(problem1, problem2, problem3)); + + // when + List problems = problemRepository.findAllByProblemStatus(ACTIVE); + + // then + assertThat(problems) + .hasSize(2) + .extracting("baekjoonProblemId", "problemTier", "problemStatus", "solvedCount") + .containsExactlyInAnyOrder( + tuple(1L, B5, ACTIVE, 0L), + tuple(2L, S5, ACTIVE, 0L) + ); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java new file mode 100644 index 00000000..22738f36 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend.domain.problem; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.problem.ProblemStatus.INIT; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class ProblemTest { + @DisplayName("문제가 처음 생성될 때, 문제의 상태는 INIT이어야 한다.") + @Test + void createProblem() { + // when + Problem problem1 = Problem.create(1L, B5, 0L); + + // then + assertThat(problem1) + .extracting("baekjoonProblemId", "problemTier", "problemStatus", "solvedCount") + .containsExactly( + 1L, B5, INIT, 0L + ); + } + + @DisplayName("문제가 활성화되면 문제의 상태는 ACTIVE여야 한다.") + @Test + void activate() { + // given + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + + // when + problem1.activate(); + + // then + assertThat(problem1.getProblemStatus()).isEqualTo(ProblemStatus.ACTIVE); + assertThat(problem2.getProblemStatus()).isEqualTo(INIT); + + } + +} \ No newline at end of file From 3147fc421dacc5b527e907c2d9a49b10db44bd08 Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Sat, 24 Feb 2024 17:53:29 +0900 Subject: [PATCH 04/14] =?UTF-8?q?:rocket:=20Jacoco=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20Sonarcloud=20=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.gradle Jacoco 관련 코드 및 coverage 정의 - 테스트용 db를 h2를 이용하기에 관련 컨테이너 코드 삭제함 --- .DS_Store | Bin 0 -> 6148 bytes .github/workflows/sonarcloud.yml | 11 +--- build.gradle | 95 +++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b9256c22f84544d40e537642b5e1778fc0d22ae8 GIT binary patch literal 6148 zcmeHK%}T>S5Z>*NO({YSiXIodR%`_;#Y>3w1&ruHr6#0kFlI}WnnNk%sxRc5_&m<+ zZlJ|@6|pn0`_0bJZsvpR4`YnG^RUmD%^0(wA#zkI1kIJMnh8eaYK}0-(`gXPpkkW8 zX~J)BvVhH5#A3Gk{U5B=Sx3$boPr+XhQ`2W6$8S8tZYGI<1RnNda(5(C5lF+dD# zCt+Z6&FH`x* zUr(VCF+dFbGX{8LpiV*6EfnQ+Y3x4uSoB#j- literal 0 HcmV?d00001 diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index a8caba0c..8096f392 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -20,16 +20,7 @@ jobs: java-version: 17 distribution: 'zulu' # Alternative distribution options are available - - name: Start MySQL container - uses: mirromutth/mysql-action@v1.1 - with: - host port: 3306 - container port: 3306 - mysql database: 'new_morandi' - mysql root password: 1234 - mysql options: '--skip-ssl' # SSL 비활성화 - - - name: Create application-dev.yml from GitHub Secret + - name: Create application-test.yml from GitHub Secret run: | mkdir -p src/main/resources echo "${{ secrets.TEST_APPLICATION_YML }}" > src/main/resources/application.yml diff --git a/build.gradle b/build.gradle index 750559cc..e0487984 100644 --- a/build.gradle +++ b/build.gradle @@ -7,14 +7,53 @@ plugins { id "org.sonarqube" version "4.4.1.3373" } +plugins { + id 'jacoco' +} + + +def jacocoDir = layout.buildDirectory.dir("reports/") +def QDomains = [] +for (qPattern in '*.QA'..'*.QZ') { // qPattern = '*.QA', '*.QB', ... '*.QZ' + QDomains.add(qPattern + '*') +} + +def jacocoExcludePatterns = [ + // 측정 안하고 싶은 패턴 + "**/*Application*", + "**/*Config*", + "**/*Exception*", + "**/*Request*", + "**/*Response*", + "**/*Dto*", + "**/*Interceptor*", + "**/*Filter*", + "**/*Resolver*", + "**/*Entity*", + "**/test/**", + "**/resources/**" +] + sonar { properties { property "sonar.projectKey", "SWM-Morandi_NewMorandi-Backend" property "sonar.organization", "swm-morandi" property "sonar.host.url", "https://sonarcloud.io" + property 'sonar.gradle.skipCompile', 'true' + property 'sonar.sources', 'src' + property 'sonar.language', 'java' + property 'sonar.sourceEncoding', 'UTF-8' + property 'sonar.test.exclusions', jacocoExcludePatterns.join(',') + property 'sonar.test.inclusions', '**/*Test.java' + property 'sonar.java.coveragePlugin', 'jacoco' + property 'sonar.coverage.jacoco.xmlReportPaths', jacocoDir.get().file("jacoco/index.xml").asFile } } +jacoco { + toolVersion = "0.8.8" +} + group = 'kr.co.morandi' version = '0.0.1-SNAPSHOT' @@ -53,6 +92,62 @@ dependencies { testImplementation 'com.h2database:h2' } +tasks.named('sonarqube').configure { + dependsOn 'compileJava' +} + tasks.named('test') { useJUnitPlatform() + finalizedBy 'jacocoTestReport' +} + + + +jacocoTestReport { + dependsOn test + reports { + html.required.set(true) + xml.required.set(true) + csv.required.set(true) + html.destination jacocoDir.get().file("jacoco/index.html").asFile + xml.destination jacocoDir.get().file("jacoco/index.xml").asFile + csv.destination jacocoDir.get().file("jacoco/index.csv").asFile + } + + afterEvaluate { + classDirectories.setFrom( + files(classDirectories.files.collect { + fileTree(dir: it, excludes: jacocoExcludePatterns + QDomains) // Querydsl 관련 제거 + }) + ) + } + finalizedBy jacocoTestCoverageVerification } + + + +jacocoTestCoverageVerification { + + violationRules { + rule { + enabled = true + + element = 'BUNDLE' + + limit { + counter = 'LINE' + value = 'COVEREDRATIO' + minimum = 0.50 + } + + limit { + counter = 'BRANCH' + value = 'COVEREDRATIO' + minimum = 0.50 + } + + + excludes = jacocoExcludePatterns + QDomains + } + } +} \ No newline at end of file From 985ad63abf752a5866aa02d787cb850b247373ef Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:38:31 +0900 Subject: [PATCH 05/14] =?UTF-8?q?Algorithm=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :art: Algorithm 필드 추가 Algorithm 테이블에 solved에서 취급하는 key와 bojTagId를 추가했습니다. * :sparkles: 어플리케이션 시작 시 알고리즘 테이블 초기화 * :white_check_mark: AlgorithmRepository Test tearDown 메서드 추가 * :bug: @ Value Algorithms.json 주소 변경 --- .../backend/config/AlgorithmInitializer.java | 51 + .../backend/domain/algorithm/Algorithm.java | 8 +- .../domain/algorithm/AlgorithmRepository.java | 3 + src/main/resources/Algorithms.json | 1032 +++++++++++++++++ .../config/AlgorithmInitializerTest.java | 61 + .../algorithm/AlgorithmRepositoryTest.java | 53 + 6 files changed, 1207 insertions(+), 1 deletion(-) create mode 100644 src/main/java/kr/co/morandi/backend/config/AlgorithmInitializer.java create mode 100644 src/main/resources/Algorithms.json create mode 100644 src/test/java/kr/co/morandi/backend/config/AlgorithmInitializerTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepositoryTest.java diff --git a/src/main/java/kr/co/morandi/backend/config/AlgorithmInitializer.java b/src/main/java/kr/co/morandi/backend/config/AlgorithmInitializer.java new file mode 100644 index 00000000..66d0b9a0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/config/AlgorithmInitializer.java @@ -0,0 +1,51 @@ +package kr.co.morandi.backend.config; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import org.springframework.core.io.Resource; +import org.springframework.beans.factory.annotation.Value; + +import kr.co.morandi.backend.domain.algorithm.Algorithm; +import kr.co.morandi.backend.domain.algorithm.AlgorithmRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class AlgorithmInitializer { + + private final AlgorithmRepository algorithmRepository; + private final ObjectMapper objectMapper; + + @Value("classpath:Algorithms.json") + private Resource algorithmsResource; + + @PostConstruct + @Transactional + public void init() throws IOException { + final List initialAlgorithms = objectMapper.readValue(algorithmsResource.getInputStream(), new TypeReference<>() {}); + final List uninitialized = collectUninitialized(initialAlgorithms); + + algorithmRepository.saveAll(uninitialized); + } + + private List collectUninitialized(List initialAlgorithms) { + final List findAll = algorithmRepository.findAll(); + + final Set collect = findAll.stream() + .map(Algorithm::getBojTagId) + .collect(Collectors.toSet()); + + return initialAlgorithms.stream() + .filter(algorithm -> !collect.contains(algorithm.getBojTagId())) + .toList(); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java b/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java index 355fa914..d3c8b5c3 100644 --- a/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java +++ b/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java @@ -15,10 +15,16 @@ public class Algorithm extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long algorithmId; + private Integer bojTagId; + + private String algorithmKey; + private String algorithmName; @Builder - private Algorithm(String algorithmName) { + private Algorithm(Integer bojTagId, String algorithmKey, String algorithmName) { + this.bojTagId = bojTagId; + this.algorithmKey = algorithmKey; this.algorithmName = algorithmName; } } diff --git a/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java index f8622dd1..e72cad9e 100644 --- a/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java +++ b/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java @@ -2,5 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; + public interface AlgorithmRepository extends JpaRepository { + Boolean existsByBojTagIdOrAlgorithmKey(Integer bojTagId, String algorithmKey); + } diff --git a/src/main/resources/Algorithms.json b/src/main/resources/Algorithms.json new file mode 100644 index 00000000..bd9d1029 --- /dev/null +++ b/src/main/resources/Algorithms.json @@ -0,0 +1,1032 @@ +[ + { + "bojTagId": 1, + "algorithmKey": "2_sat", + "algorithmName": "2-sat" + }, + { + "bojTagId": 2, + "algorithmKey": "aho_corasick", + "algorithmName": "아호-코라식" + }, + { + "bojTagId": 3, + "algorithmKey": "polygon_area", + "algorithmName": "다각형의 넓이" + }, + { + "bojTagId": 4, + "algorithmKey": "articulation", + "algorithmName": "단절점과 단절선" + }, + { + "bojTagId": 5, + "algorithmKey": "backtracking", + "algorithmName": "백트래킹" + }, + { + "bojTagId": 6, + "algorithmKey": "combinatorics", + "algorithmName": "조합론" + }, + { + "bojTagId": 7, + "algorithmKey": "graphs", + "algorithmName": "그래프 이론" + }, + { + "bojTagId": 8, + "algorithmKey": "hashing", + "algorithmName": "해싱" + }, + { + "bojTagId": 9, + "algorithmKey": "primality_test", + "algorithmName": "소수 판정" + }, + { + "bojTagId": 10, + "algorithmKey": "bellman_ford", + "algorithmName": "벨만–포드" + }, + { + "bojTagId": 11, + "algorithmKey": "graph_traversal", + "algorithmName": "그래프 탐색" + }, + { + "bojTagId": 12, + "algorithmKey": "binary_search", + "algorithmName": "이분 탐색" + }, + { + "bojTagId": 13, + "algorithmKey": "bipartite_matching", + "algorithmName": "이분 매칭" + }, + { + "bojTagId": 14, + "algorithmKey": "bitmask", + "algorithmName": "비트마스킹" + }, + { + "bojTagId": 15, + "algorithmKey": "general_matching", + "algorithmName": "일반적인 매칭" + }, + { + "bojTagId": 16, + "algorithmKey": "burnside", + "algorithmName": "번사이드 보조정리" + }, + { + "bojTagId": 18, + "algorithmKey": "centroid_decomposition", + "algorithmName": "센트로이드 분할" + }, + { + "bojTagId": 19, + "algorithmKey": "crt", + "algorithmName": "중국인의 나머지 정리" + }, + { + "bojTagId": 20, + "algorithmKey": "convex_hull", + "algorithmName": "볼록 껍질" + }, + { + "bojTagId": 21, + "algorithmKey": "delaunay", + "algorithmName": "델로네 삼각분할" + }, + { + "bojTagId": 22, + "algorithmKey": "dijkstra", + "algorithmName": "데이크스트라" + }, + { + "bojTagId": 23, + "algorithmKey": "directed_mst", + "algorithmName": "유향 최소 신장 트리" + }, + { + "bojTagId": 24, + "algorithmKey": "divide_and_conquer", + "algorithmName": "분할 정복" + }, + { + "bojTagId": 25, + "algorithmKey": "dp", + "algorithmName": "다이나믹 프로그래밍" + }, + { + "bojTagId": 26, + "algorithmKey": "euclidean", + "algorithmName": "유클리드 호제법" + }, + { + "bojTagId": 27, + "algorithmKey": "extended_euclidean", + "algorithmName": "확장 유클리드 호제법" + }, + { + "bojTagId": 28, + "algorithmKey": "fft", + "algorithmName": "고속 푸리에 변환" + }, + { + "bojTagId": 29, + "algorithmKey": "flt", + "algorithmName": "페르마의 소정리" + }, + { + "bojTagId": 31, + "algorithmKey": "floyd_warshall", + "algorithmName": "플로이드–워셜" + }, + { + "bojTagId": 32, + "algorithmKey": "gaussian_elimination", + "algorithmName": "가우스 소거법" + }, + { + "bojTagId": 33, + "algorithmKey": "greedy", + "algorithmName": "그리디 알고리즘" + }, + { + "bojTagId": 34, + "algorithmKey": "hall", + "algorithmName": "홀의 결혼 정리" + }, + { + "bojTagId": 35, + "algorithmKey": "hld", + "algorithmName": "Heavy-light 분할" + }, + { + "bojTagId": 36, + "algorithmKey": "hungarian", + "algorithmName": "헝가리안" + }, + { + "bojTagId": 38, + "algorithmKey": "inclusion_and_exclusion", + "algorithmName": "포함 배제의 원리" + }, + { + "bojTagId": 39, + "algorithmKey": "exponentiation_by_squaring", + "algorithmName": "분할 정복을 이용한 거듭제곱" + }, + { + "bojTagId": 40, + "algorithmKey": "kmp", + "algorithmName": "KMP" + }, + { + "bojTagId": 41, + "algorithmKey": "lca", + "algorithmName": "최소 공통 조상" + }, + { + "bojTagId": 42, + "algorithmKey": "line_intersection", + "algorithmName": "선분 교차 판정" + }, + { + "bojTagId": 43, + "algorithmKey": "lis", + "algorithmName": "가장 긴 증가하는 부분 수열: O(n log n)" + }, + { + "bojTagId": 44, + "algorithmKey": "manacher", + "algorithmName": "매내처" + }, + { + "bojTagId": 45, + "algorithmKey": "flow", + "algorithmName": "최대 유량" + }, + { + "bojTagId": 46, + "algorithmKey": "mitm", + "algorithmName": "중간에서 만나기" + }, + { + "bojTagId": 47, + "algorithmKey": "miller_rabin", + "algorithmName": "밀러–라빈 소수 판별법" + }, + { + "bojTagId": 48, + "algorithmKey": "mcmf", + "algorithmName": "최소 비용 최대 유량" + }, + { + "bojTagId": 49, + "algorithmKey": "mst", + "algorithmName": "최소 스패닝 트리" + }, + { + "bojTagId": 50, + "algorithmKey": "mo", + "algorithmName": "mo\\'s" + }, + { + "bojTagId": 51, + "algorithmKey": "mobius_inversion", + "algorithmName": "뫼비우스 반전 공식" + }, + { + "bojTagId": 52, + "algorithmKey": "offline_dynamic_connectivity", + "algorithmName": "오프라인 동적 연결성 판정" + }, + { + "bojTagId": 53, + "algorithmKey": "palindrome_tree", + "algorithmName": "회문 트리" + }, + { + "bojTagId": 54, + "algorithmKey": "pbs", + "algorithmName": "병렬 이분 탐색" + }, + { + "bojTagId": 55, + "algorithmKey": "pst", + "algorithmName": "퍼시스턴트 세그먼트 트리" + }, + { + "bojTagId": 56, + "algorithmKey": "point_in_convex_polygon", + "algorithmName": "볼록 다각형 내부의 점 판정" + }, + { + "bojTagId": 57, + "algorithmKey": "point_in_non_convex_polygon", + "algorithmName": "오목 다각형 내부의 점 판정" + }, + { + "bojTagId": 58, + "algorithmKey": "pollard_rho", + "algorithmName": "폴라드 로" + }, + { + "bojTagId": 59, + "algorithmKey": "priority_queue", + "algorithmName": "우선순위 큐" + }, + { + "bojTagId": 60, + "algorithmKey": "pythagoras", + "algorithmName": "피타고라스 정리" + }, + { + "bojTagId": 61, + "algorithmKey": "rabin_karp", + "algorithmName": "라빈–카프" + }, + { + "bojTagId": 62, + "algorithmKey": "recursion", + "algorithmName": "재귀" + }, + { + "bojTagId": 63, + "algorithmKey": "regex", + "algorithmName": "정규 표현식" + }, + { + "bojTagId": 64, + "algorithmKey": "rotating_calipers", + "algorithmName": "회전하는 캘리퍼스" + }, + { + "bojTagId": 65, + "algorithmKey": "segtree", + "algorithmName": "세그먼트 트리" + }, + { + "bojTagId": 66, + "algorithmKey": "lazyprop", + "algorithmName": "느리게 갱신되는 세그먼트 트리" + }, + { + "bojTagId": 67, + "algorithmKey": "sieve", + "algorithmName": "에라토스테네스의 체" + }, + { + "bojTagId": 68, + "algorithmKey": "sliding_window", + "algorithmName": "슬라이딩 윈도우" + }, + { + "bojTagId": 69, + "algorithmKey": "splay_tree", + "algorithmName": "스플레이 트리" + }, + { + "bojTagId": 70, + "algorithmKey": "sprague_grundy", + "algorithmName": "스프라그–그런디 정리" + }, + { + "bojTagId": 71, + "algorithmKey": "stack", + "algorithmName": "스택" + }, + { + "bojTagId": 72, + "algorithmKey": "queue", + "algorithmName": "큐" + }, + { + "bojTagId": 73, + "algorithmKey": "deque", + "algorithmName": "덱" + }, + { + "bojTagId": 74, + "algorithmKey": "tree_set", + "algorithmName": "트리를 사용한 집합과 맵" + }, + { + "bojTagId": 75, + "algorithmKey": "stoer_wagner", + "algorithmName": "스토어–바그너" + }, + { + "bojTagId": 76, + "algorithmKey": "scc", + "algorithmName": "강한 연결 요소" + }, + { + "bojTagId": 77, + "algorithmKey": "suffix_array", + "algorithmName": "접미사 배열과 LCP 배열" + }, + { + "bojTagId": 78, + "algorithmKey": "topological_sorting", + "algorithmName": "위상 정렬" + }, + { + "bojTagId": 79, + "algorithmKey": "trie", + "algorithmName": "트라이" + }, + { + "bojTagId": 80, + "algorithmKey": "two_pointer", + "algorithmName": "두 포인터" + }, + { + "bojTagId": 81, + "algorithmKey": "disjoint_set", + "algorithmName": "분리 집합" + }, + { + "bojTagId": 82, + "algorithmKey": "voronoi", + "algorithmName": "보로노이 다이어그램" + }, + { + "bojTagId": 83, + "algorithmKey": "z", + "algorithmName": "z" + }, + { + "bojTagId": 84, + "algorithmKey": "sparse_table", + "algorithmName": "희소 배열" + }, + { + "bojTagId": 87, + "algorithmKey": "dp_bitfield", + "algorithmName": "비트필드를 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 89, + "algorithmKey": "cht", + "algorithmName": "볼록 껍질을 이용한 최적화" + }, + { + "bojTagId": 90, + "algorithmKey": "knuth", + "algorithmName": "크누스 최적화" + }, + { + "bojTagId": 91, + "algorithmKey": "divide_and_conquer_optimization", + "algorithmName": "분할 정복을 사용한 최적화" + }, + { + "bojTagId": 92, + "algorithmKey": "dp_tree", + "algorithmName": "트리에서의 다이나믹 프로그래밍" + }, + { + "bojTagId": 93, + "algorithmKey": "eulerian_path", + "algorithmName": "오일러 경로" + }, + { + "bojTagId": 94, + "algorithmKey": "rb_tree", + "algorithmName": "레드-블랙 트리" + }, + { + "bojTagId": 95, + "algorithmKey": "number_theory", + "algorithmName": "정수론" + }, + { + "bojTagId": 96, + "algorithmKey": "parsing", + "algorithmName": "파싱" + }, + { + "bojTagId": 97, + "algorithmKey": "sorting", + "algorithmName": "정렬" + }, + { + "bojTagId": 98, + "algorithmKey": "link_cut_tree", + "algorithmName": "링크/컷 트리" + }, + { + "bojTagId": 100, + "algorithmKey": "geometry", + "algorithmName": "기하학" + }, + { + "bojTagId": 101, + "algorithmKey": "ternary_search", + "algorithmName": "삼분 탐색" + }, + { + "bojTagId": 102, + "algorithmKey": "implementation", + "algorithmName": "구현" + }, + { + "bojTagId": 103, + "algorithmKey": "linear_programming", + "algorithmName": "선형 계획법" + }, + { + "bojTagId": 104, + "algorithmKey": "matroid", + "algorithmName": "매트로이드" + }, + { + "bojTagId": 105, + "algorithmKey": "top_tree", + "algorithmName": "탑 트리" + }, + { + "bojTagId": 106, + "algorithmKey": "sweeping", + "algorithmName": "스위핑" + }, + { + "bojTagId": 107, + "algorithmKey": "dp_connection_profile", + "algorithmName": "커넥션 프로파일을 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 108, + "algorithmKey": "dp_deque", + "algorithmName": "덱을 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 109, + "algorithmKey": "ad_hoc", + "algorithmName": "애드 혹" + }, + { + "bojTagId": 110, + "algorithmKey": "berlekamp_massey", + "algorithmName": "벌리캠프–매시" + }, + { + "bojTagId": 111, + "algorithmKey": "calculus", + "algorithmName": "미적분학" + }, + { + "bojTagId": 112, + "algorithmKey": "kitamasa", + "algorithmName": "키타마사" + }, + { + "bojTagId": 113, + "algorithmKey": "lucas", + "algorithmName": "뤼카 정리" + }, + { + "bojTagId": 114, + "algorithmKey": "bayes", + "algorithmName": "베이즈 정리" + }, + { + "bojTagId": 115, + "algorithmKey": "randomization", + "algorithmName": "무작위화" + }, + { + "bojTagId": 116, + "algorithmKey": "physics", + "algorithmName": "물리학" + }, + { + "bojTagId": 117, + "algorithmKey": "arbitrary_precision", + "algorithmName": "임의 정밀도 / 큰 수 연산" + }, + { + "bojTagId": 119, + "algorithmKey": "euler_characteristic", + "algorithmName": "오일러 지표 (χ=V-E+F)" + }, + { + "bojTagId": 120, + "algorithmKey": "trees", + "algorithmName": "트리" + }, + { + "bojTagId": 121, + "algorithmKey": "arithmetic", + "algorithmName": "사칙연산" + }, + { + "bojTagId": 122, + "algorithmKey": "numerical_analysis", + "algorithmName": "수치해석" + }, + { + "bojTagId": 123, + "algorithmKey": "offline_queries", + "algorithmName": "오프라인 쿼리" + }, + { + "bojTagId": 124, + "algorithmKey": "math", + "algorithmName": "수학" + }, + { + "bojTagId": 125, + "algorithmKey": "bruteforcing", + "algorithmName": "브루트포스 알고리즘" + }, + { + "bojTagId": 126, + "algorithmKey": "bfs", + "algorithmName": "너비 우선 탐색" + }, + { + "bojTagId": 127, + "algorithmKey": "dfs", + "algorithmName": "깊이 우선 탐색" + }, + { + "bojTagId": 128, + "algorithmKey": "constructive", + "algorithmName": "해 구성하기" + }, + { + "bojTagId": 129, + "algorithmKey": "bidirectional_search", + "algorithmName": "양방향 탐색" + }, + { + "bojTagId": 130, + "algorithmKey": "sqrt_decomposition", + "algorithmName": "제곱근 분할법" + }, + { + "bojTagId": 131, + "algorithmKey": "geometry_3d", + "algorithmName": "3차원 기하학" + }, + { + "bojTagId": 132, + "algorithmKey": "geometry_hyper", + "algorithmName": "4차원 이상의 기하학" + }, + { + "bojTagId": 134, + "algorithmKey": "alien", + "algorithmName": "Aliens 트릭" + }, + { + "bojTagId": 135, + "algorithmKey": "dominator_tree", + "algorithmName": "도미네이터 트리" + }, + { + "bojTagId": 136, + "algorithmKey": "hash_set", + "algorithmName": "해시를 사용한 집합과 맵" + }, + { + "bojTagId": 137, + "algorithmKey": "case_work", + "algorithmName": "많은 조건 분기" + }, + { + "bojTagId": 138, + "algorithmKey": "tsp", + "algorithmName": "외판원 순회 문제" + }, + { + "bojTagId": 139, + "algorithmKey": "prefix_sum", + "algorithmName": "누적 합" + }, + { + "bojTagId": 140, + "algorithmKey": "game_theory", + "algorithmName": "게임 이론" + }, + { + "bojTagId": 141, + "algorithmKey": "simulation", + "algorithmName": "시뮬레이션" + }, + { + "bojTagId": 142, + "algorithmKey": "heuristics", + "algorithmName": "휴리스틱" + }, + { + "bojTagId": 143, + "algorithmKey": "cactus", + "algorithmName": "선인장" + }, + { + "bojTagId": 144, + "algorithmKey": "linear_algebra", + "algorithmName": "선형대수학" + }, + { + "bojTagId": 145, + "algorithmKey": "tree_isomorphism", + "algorithmName": "트리 동형 사상" + }, + { + "bojTagId": 146, + "algorithmKey": "discrete_log", + "algorithmName": "이산 로그" + }, + { + "bojTagId": 147, + "algorithmKey": "discrete_sqrt", + "algorithmName": "이산 제곱근" + }, + { + "bojTagId": 148, + "algorithmKey": "knapsack", + "algorithmName": "배낭 문제" + }, + { + "bojTagId": 149, + "algorithmKey": "discrete_kth_root", + "algorithmName": "이산 k제곱근" + }, + { + "bojTagId": 150, + "algorithmKey": "euler_tour_technique", + "algorithmName": "오일러 경로 테크닉" + }, + { + "bojTagId": 151, + "algorithmKey": "euler_phi", + "algorithmName": "오일러 피 함수" + }, + { + "bojTagId": 152, + "algorithmKey": "bitset", + "algorithmName": "비트 집합" + }, + { + "bojTagId": 153, + "algorithmKey": "biconnected_component", + "algorithmName": "이중 연결 요소" + }, + { + "bojTagId": 154, + "algorithmKey": "linked_list", + "algorithmName": "연결 리스트" + }, + { + "bojTagId": 155, + "algorithmKey": "merge_sort_tree", + "algorithmName": "머지 소트 트리" + }, + { + "bojTagId": 157, + "algorithmKey": "slope_trick", + "algorithmName": "함수 개형을 이용한 최적화" + }, + { + "bojTagId": 158, + "algorithmKey": "string", + "algorithmName": "문자열" + }, + { + "bojTagId": 159, + "algorithmKey": "rope", + "algorithmName": "로프" + }, + { + "bojTagId": 160, + "algorithmKey": "majority_vote", + "algorithmName": "보이어–무어 다수결 투표" + }, + { + "bojTagId": 161, + "algorithmKey": "coordinate_compression", + "algorithmName": "값 / 좌표 압축" + }, + { + "bojTagId": 162, + "algorithmKey": "min_enclosing_circle", + "algorithmName": "최소 외접원" + }, + { + "bojTagId": 163, + "algorithmKey": "hirschberg", + "algorithmName": "히르쉬버그" + }, + { + "bojTagId": 164, + "algorithmKey": "modular_multiplicative_inverse", + "algorithmName": "모듈로 곱셈 역원" + }, + { + "bojTagId": 165, + "algorithmKey": "monotone_queue_optimization", + "algorithmName": "단조 큐를 이용한 최적화" + }, + { + "bojTagId": 166, + "algorithmKey": "multi_segtree", + "algorithmName": "다차원 세그먼트 트리" + }, + { + "bojTagId": 167, + "algorithmKey": "mfmc", + "algorithmName": "최대 유량 최소 컷 정리" + }, + { + "bojTagId": 168, + "algorithmKey": "planar_graph", + "algorithmName": "평면 그래프" + }, + { + "bojTagId": 169, + "algorithmKey": "smaller_to_larger", + "algorithmName": "작은 집합에서 큰 집합으로 합치는 테크닉" + }, + { + "bojTagId": 170, + "algorithmKey": "parametric_search", + "algorithmName": "매개 변수 탐색" + }, + { + "bojTagId": 171, + "algorithmKey": "permutation_cycle_decomposition", + "algorithmName": "순열 사이클 분할" + }, + { + "bojTagId": 172, + "algorithmKey": "precomputation", + "algorithmName": "런타임 전의 전처리" + }, + { + "bojTagId": 173, + "algorithmKey": "dancing_links", + "algorithmName": "춤추는 링크" + }, + { + "bojTagId": 174, + "algorithmKey": "knuth_x", + "algorithmName": "크누스 X" + }, + { + "bojTagId": 175, + "algorithmKey": "data_structures", + "algorithmName": "자료 구조" + }, + { + "bojTagId": 176, + "algorithmKey": "0_1_bfs", + "algorithmName": "0-1 너비 우선 탐색" + }, + { + "bojTagId": 177, + "algorithmKey": "probability", + "algorithmName": "확률론" + }, + { + "bojTagId": 178, + "algorithmKey": "statistics", + "algorithmName": "통계학" + }, + { + "bojTagId": 179, + "algorithmKey": "linearity_of_expectation", + "algorithmName": "기댓값의 선형성" + }, + { + "bojTagId": 180, + "algorithmKey": "duality", + "algorithmName": "쌍대성" + }, + { + "bojTagId": 181, + "algorithmKey": "dual_graph", + "algorithmName": "쌍대 그래프" + }, + { + "bojTagId": 182, + "algorithmKey": "suffix_tree", + "algorithmName": "접미사 트리" + }, + { + "bojTagId": 183, + "algorithmKey": "green", + "algorithmName": "그린 정리" + }, + { + "bojTagId": 184, + "algorithmKey": "simulated_annealing", + "algorithmName": "담금질 기법" + }, + { + "bojTagId": 185, + "algorithmKey": "differential_cryptanalysis", + "algorithmName": "차분 공격" + }, + { + "bojTagId": 186, + "algorithmKey": "a_star", + "algorithmName": "a*" + }, + { + "bojTagId": 187, + "algorithmKey": "pick", + "algorithmName": "픽의 정리" + }, + { + "bojTagId": 188, + "algorithmKey": "centroid", + "algorithmName": "센트로이드" + }, + { + "bojTagId": 189, + "algorithmKey": "pigeonhole_principle", + "algorithmName": "비둘기집 원리" + }, + { + "bojTagId": 190, + "algorithmKey": "half_plane_intersection", + "algorithmName": "반평면 교집합" + }, + { + "bojTagId": 191, + "algorithmKey": "circulation", + "algorithmName": "서큘레이션" + }, + { + "bojTagId": 192, + "algorithmKey": "stable_marriage", + "algorithmName": "안정 결혼 문제" + }, + { + "bojTagId": 193, + "algorithmKey": "tree_compression", + "algorithmName": "트리 압축" + }, + { + "bojTagId": 196, + "algorithmKey": "multipoint_evaluation", + "algorithmName": "다중 대입값 계산" + }, + { + "bojTagId": 197, + "algorithmKey": "bipartite_graph", + "algorithmName": "이분 그래프" + }, + { + "bojTagId": 198, + "algorithmKey": "generating_function", + "algorithmName": "생성 함수" + }, + { + "bojTagId": 199, + "algorithmKey": "utf8", + "algorithmName": "utf-8 입력 처리" + }, + { + "bojTagId": 200, + "algorithmKey": "degree_sequence", + "algorithmName": "차수열" + }, + { + "bojTagId": 201, + "algorithmKey": "chordal_graph", + "algorithmName": "현 그래프" + }, + { + "bojTagId": 202, + "algorithmKey": "geometric_boolean_operations", + "algorithmName": "도형에서의 불 연산" + }, + { + "bojTagId": 203, + "algorithmKey": "birthday", + "algorithmName": "생일 문제" + }, + { + "bojTagId": 204, + "algorithmKey": "tree_decomposition", + "algorithmName": "트리 분할" + }, + { + "bojTagId": 205, + "algorithmKey": "hackenbush", + "algorithmName": "하켄부시 게임" + }, + { + "bojTagId": 206, + "algorithmKey": "cartesian_tree", + "algorithmName": "데카르트 트리" + }, + { + "bojTagId": 207, + "algorithmKey": "dp_sum_over_subsets", + "algorithmName": "부분집합의 합 다이나믹 프로그래밍" + }, + { + "bojTagId": 208, + "algorithmKey": "gradient_descent", + "algorithmName": "경사 하강법" + }, + { + "bojTagId": 209, + "algorithmKey": "polynomial_interpolation", + "algorithmName": "다항식 보간법" + }, + { + "bojTagId": 210, + "algorithmKey": "flood_fill", + "algorithmName": "플러드 필" + }, + { + "bojTagId": 211, + "algorithmKey": "functional_graph", + "algorithmName": "함수형 그래프" + }, + { + "bojTagId": 212, + "algorithmKey": "lte", + "algorithmName": "지수승강 보조정리" + }, + { + "bojTagId": 213, + "algorithmKey": "dag", + "algorithmName": "방향 비순환 그래프" + }, + { + "bojTagId": 214, + "algorithmKey": "lgv", + "algorithmName": "린드스트롬–게셀–비엔노 보조정리" + }, + { + "bojTagId": 215, + "algorithmKey": "shortest_path", + "algorithmName": "최단 경로" + }, + { + "bojTagId": 216, + "algorithmKey": "deque_trick", + "algorithmName": "덱을 이용한 구간 최댓값 트릭" + }, + { + "bojTagId": 217, + "algorithmKey": "dp_digit", + "algorithmName": "자릿수를 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 218, + "algorithmKey": "floor_sum", + "algorithmName": "유리 등차수열의 내림 합" + } +] \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/config/AlgorithmInitializerTest.java b/src/test/java/kr/co/morandi/backend/config/AlgorithmInitializerTest.java new file mode 100644 index 00000000..0c455296 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/config/AlgorithmInitializerTest.java @@ -0,0 +1,61 @@ +package kr.co.morandi.backend.config; + +import kr.co.morandi.backend.domain.algorithm.Algorithm; +import kr.co.morandi.backend.domain.algorithm.AlgorithmRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class AlgorithmInitializerTest { + @Autowired + private AlgorithmInitializer algorithmInitializer; + + @Autowired + private AlgorithmRepository algorithmRepository; + @AfterEach + void tearDown() { + algorithmRepository.deleteAllInBatch(); + } + @DisplayName("알고리즘 시드 데이터가 초기화가 정상적으로 이루어진다.") + @Test + void whenDatabaseIsEmpty_thenInitializesDataSuccessfully() throws IOException { + // when + algorithmInitializer.init(); + + // then + List allAlgorithms = algorithmRepository.findAll(); + + assertThat(allAlgorithms).hasSizeGreaterThan(0) + .extracting("bojTagId", "algorithmKey", "algorithmName") + .containsAnyOf( + tuple(1, "2_sat", "2-sat"), + tuple(218, "floor_sum", "유리 등차수열의 내림 합") + ); + + + } + @DisplayName("데이터베이스에 데이터가 이미 초기화되어 있을 때 중복 초기화가 방지된다.") + @Test + void whenDataAlreadyExists_thenPreventsDuplicateInitialization() throws IOException { + // given + algorithmInitializer.init(); + long initialCount = algorithmRepository.count(); + + // when + algorithmInitializer.init(); + + // then + assertThat(algorithmRepository.count()).isEqualTo(initialCount); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepositoryTest.java new file mode 100644 index 00000000..52d68c30 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepositoryTest.java @@ -0,0 +1,53 @@ +package kr.co.morandi.backend.domain.algorithm; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class AlgorithmRepositoryTest { + + @Autowired + private AlgorithmRepository algorithmRepository; + @AfterEach + void tearDown() { + algorithmRepository.deleteAllInBatch(); + } + + @DisplayName("알고리즘 초기 데이터가 존재하는지 확인한다.") + @Test + void existsByBojTagIdOrAlgorithmKey() { + // given + Algorithm algorithm1 = Algorithm.builder() + .bojTagId(1) + .algorithmKey("key1") + .algorithmName("name1") + .build(); + + Algorithm algorithm2 = Algorithm.builder() + .bojTagId(2) + .algorithmKey("key2") + .algorithmName("name2") + .build(); + + algorithmRepository.saveAll(List.of(algorithm1, algorithm2)); + + // when + boolean exists1 = algorithmRepository.existsByBojTagIdOrAlgorithmKey(1, "key1"); + boolean exists2 = algorithmRepository.existsByBojTagIdOrAlgorithmKey(2, "key2"); + + + // then + assertThat(exists1).isTrue(); + assertThat(exists2).isTrue(); + } + +} \ No newline at end of file From 50c306f4b33a7ad4a431687538a2d325920a119b Mon Sep 17 00:00:00 2001 From: Jeong Yong Choi <51875177+aj4941@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:34:56 +0900 Subject: [PATCH 06/14] =?UTF-8?q?ContentRecord=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EB=A7=A4=ED=95=91=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: ContentRecord 데이터베이스 매핑 * :white_check_mark: 커스텀 디펜스 기록과 관련한 테스트 추가 및 관련 코드 일부 수정 * :white_check_mark: 커스텀 디펜스 기록과 관련한 테스트 코드 수정 * :art: 테스트 기록 문제 생성의 책임을 테스트 기록 클래스로 이동 추상 클래스에서 추상 메서드 정의 및 Override를 통해 문제 리스트 생성의 책임을 ContentRecord로 옮겼습니다. * :recycle: 커스텀 디펜스 기록과 관련 양방향 매핑 및 테스트 코드 수정 * :sparkles: 오늘의 문제 관련 테이블의 테스트 코드 작성 * :sparkles: 랜덤 디펜스 기록과 관련한 테스트 코드 작성 * :sparkles: 스테이지 모드 관련 테스트 코드 작성 * :pencil2: DisplayName에서 오늘의 문제 테스트를 오늘의 문제 테스트 응시 기록으로 변경 * :recycle: ContentProblemRecord와 관련한 정적 팩토리 메서드 패턴 적용 * :recycle: DailyTestProblems, CustomDefenseProblems에 대한 정적 펙토리 매서드 패턴 적용 * :pencil2: RandomDefense를 만드는 메서드를 따로 분리 * :recycle: 오늘의 문제 테스트 코드에서 세부 기록까지 DailyTestRecordTest에서 검증하도록 수정 * :recycle: 커스텀 랜덤 디펜스의 테스트 코드에서 세부 기록까지 CustomDefenseRecordTest에서 검증하도록 수정 * :recycle: 랜덤 디펜스의 테스트 코드에서 세부 기록까지 RandomDefenseRecordTest에서 검증하도록 수정 * :recycle: 스테이지 모드의 테스트 코드에서 세부 기록까지 StageDefenseRecordTest에서 검증하도록 수정 * :pencil2: 개행과 관련한 코드 일부 수정 * :recycle: 오늘의 문제 기록 예외와 관련된 경계값 테스트 추가 * :pencil2: 오늘의 문제 기록이 하루 이상 넘어가지 않을 때 정상적으로 등록되는 경계값 테스트 수정 * Algorithm 테이블 초기화 기능 추가 (#21) * :art: Algorithm 필드 추가 Algorithm 테이블에 solved에서 취급하는 key와 bojTagId를 추가했습니다. * :sparkles: 어플리케이션 시작 시 알고리즘 테이블 초기화 * :white_check_mark: AlgorithmRepository Test tearDown 메서드 추가 * :bug: @ Value Algorithms.json 주소 변경 * :pencil2: 스테이지 디펜스 기록 테스트와 관련하여 스테이지 번호를 시작 시 1번으로 고정 * :white_check_mark: RandomStageProblemRecord 유닛 테스트 추가, 초기 값 상수 설정 * :white_check_mark: CustomDefenseProblemRecord 유닛 테스트 추가 * :white_check_mark: DailyTestProblemRecord 테스트 코드 생성 * :white_check_mark: RandomDefenseProblemRecord 테스트 코드 생성 * :art: ContentProblemRecord 매직넘버 상수로 변경 * :fire: 필요 없는 SpringBootTest 어노테이션 제거 * :white_check_mark: StageDefenseProblemRecordTest 추가 * :white_check_mark: RandomDefenseProblemRecordTest 추가 * :white_check_mark: DailyTestProblemRecordTest 변경 * :white_check_mark: CustomDefenseRecordTest @SpringBootTest 제거 * :white_check_mark: DailyTestRecordTest 수정 * :art: RandomDefense problemCount Integer로 변경 및 테스트 코드 작성 * :art: DailyTest problemCount Integer 변경 및 테스트 코드 수정 * :white_check_mark: StageDefenseRecordTest 수정 * :pencil2: Sonarcloud issue 해결 * :fire: Unused import 제거 * :white_check_mark: 테스트 독립성을 위해 정적 팩토리 메소드 빌더로 변경 StageDefenseProblemRecordTest * :art: Member builder 생성자 id 제거 * :white_check_mark: DailyTestProblemRecordTest 테스트 given builder로 변경 * :white_check_mark: CustomDefenseRecordTest when절 수정 * :art: Initial Solved Time 상수 변경 * :white_check_mark: DailyTest Test코드 SpringBootTest 제거 * :white_check_mark: DailyTestProblemsTest SpringBootTest 제거 * :fire: RandomStageDefenseTest SpringBootTest 제거 * :white_check_mark: CustomDefenseTest SpringBootTest 제거 * :white_check_mark: RandomDefenseTest 에서 SpringBootTest 제거 --------- Co-authored-by: miiiinju1 Co-authored-by: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> --- .../ContentProblemRecord.java | 56 +++++ .../CustomDefenseProblemRecord.java | 34 +++ .../CustomDefenseProblemRecordRepository.java | 6 + .../dailytest/DailyTestProblemRecord.java | 28 +++ .../random/RandomDefenseProblemRecord.java | 35 ++++ .../random/StageDefenseProblemRecord.java | 34 +++ .../submit/SubmitCode.java | 33 +++ .../domain/contentrecord/ContentRecord.java | 51 +++++ .../customdefense/CustomDefenseRecord.java | 44 ++++ .../CustomDefenseRecordRepository.java | 6 + .../dailytest/DailyTestRecord.java | 49 +++++ .../random/RandomDefenseRecord.java | 49 +++++ .../random/StageDefenseRecord.java | 46 +++++ .../domain/contenttype/ContentType.java | 1 - .../customdefense/CustomDefense.java | 10 +- .../customdefense/CustomDefenseProblems.java | 12 +- .../contenttype/dailytest/DailyTest.java | 23 +++ .../dailytest/DailyTestProblems.java | 22 +- .../random/randomcriteria/RandomCriteria.java | 4 - .../random/randomdefense/RandomDefense.java | 25 ++- .../RandomStageDefense.java | 16 +- .../morandi/backend/domain/member/Member.java | 3 +- .../CustomDefenseProblemRecordTest.java | 84 ++++++++ .../dailytest/DailyTestProblemRecordTest.java | 133 ++++++++++++ .../RandomDefenseProblemRecordTest.java | 104 ++++++++++ .../random/StageDefenseProblemRecordTest.java | 138 +++++++++++++ .../CustomDefenseRecordTest.java | 194 ++++++++++++++++++ .../dailytest/DailyTestRecordTest.java | 173 ++++++++++++++++ .../random/RandomDefenseRecordTest.java | 193 +++++++++++++++++ .../random/StageDefenseRecordTest.java | 166 +++++++++++++++ .../customdefense/CustomDefenseTest.java | 35 +--- .../dailytest/DailyTestProblemsTest.java | 51 +++++ .../contenttype/dailytest/DailyTestTest.java | 61 ++++++ .../randomdefense/RandomDefenseTest.java | 56 +++++ .../RandomStageDefenseTest.java | 70 +++++++ .../RandomDefenseRepositoryTest.java | 18 +- 36 files changed, 1987 insertions(+), 76 deletions(-) create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/ContentProblemRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/submit/SubmitCode.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseTest.java diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/ContentProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/ContentProblemRecord.java new file mode 100644 index 00000000..ae5a61de --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/ContentProblemRecord.java @@ -0,0 +1,56 @@ +package kr.co.morandi.backend.domain.contentproblemrecord; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class ContentProblemRecord extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long contentProblemRecordId; + + private Boolean isSolved; + + private Long submitCount; + + private String solvedCode; + + @ManyToOne(fetch = FetchType.LAZY) + private ContentType contentType; + + @ManyToOne(fetch = FetchType.LAZY) + private ContentRecord contentRecord; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + private Problem problem; + + private static final Long INITIAL_SUBMIT_COUNT = 0L; + private static final Boolean INITIAL_IS_SOLVED = false; + + protected ContentProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + this.isSolved = INITIAL_IS_SOLVED; + this.submitCount = INITIAL_SUBMIT_COUNT; + this.solvedCode = null; + this.contentType = contentType; + this.contentRecord = contentRecord; + this.member = member; + this.problem = problem; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java new file mode 100644 index 00000000..88703e54 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.customdefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("CustomDefenseProblemRecord") +public class CustomDefenseProblemRecord extends ContentProblemRecord { + + private Long solvedTime; + + private static final long INITIAL_SOLVED_TIME = 0L; + private CustomDefenseProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + super(member, problem, contentRecord, contentType); + this.solvedTime = INITIAL_SOLVED_TIME; + } + public static CustomDefenseProblemRecord create(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return new CustomDefenseProblemRecord(member, problem, contentRecord, contentType); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java new file mode 100644 index 00000000..810ca4c2 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.customdefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDefenseProblemRecordRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java new file mode 100644 index 00000000..daecf75d --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java @@ -0,0 +1,28 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.dailytest; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("DailyTestProblemRecord") +public class DailyTestProblemRecord extends ContentProblemRecord { + + private DailyTestProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + super(member, problem, contentRecord, contentType); + } + public static DailyTestProblemRecord create(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return new DailyTestProblemRecord(member, problem, contentRecord, contentType); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java new file mode 100644 index 00000000..f934ba6d --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java @@ -0,0 +1,35 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.random; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("RandomDefenseProblemRecord") +public class RandomDefenseProblemRecord extends ContentProblemRecord { + + private Long solvedTime; + + private static final long INITIAL_SOLVED_TIME = 0L; + private RandomDefenseProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + super(member, problem, contentRecord, contentType); + this.solvedTime = INITIAL_SOLVED_TIME; + } + + public static RandomDefenseProblemRecord create(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return new RandomDefenseProblemRecord(member, problem, contentRecord, contentType); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java new file mode 100644 index 00000000..9896a994 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.random; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("StageDefenseProblemRecord") +public class StageDefenseProblemRecord extends ContentProblemRecord { + private Long solvedTime; + private Long stageNumber; + + private StageDefenseProblemRecord(Long stageNumber, Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + super(member, problem, contentRecord, contentType); + this.solvedTime = 0L; + this.stageNumber = stageNumber; + } + public static StageDefenseProblemRecord create(Long stageNumber, Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return new StageDefenseProblemRecord(stageNumber, member, problem, contentRecord, contentType); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/submit/SubmitCode.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/submit/SubmitCode.java new file mode 100644 index 00000000..c4159298 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/submit/SubmitCode.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.submit; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.member.Member; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SubmitCode { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long submitRecordId; + + private String submitCodeLink; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + private ContentProblemRecord contentProblemRecord; + + @Builder + private SubmitCode(String submitCodeLink, Member member, ContentProblemRecord contentProblemRecord) { + this.submitCodeLink = submitCodeLink; + this.member = member; + this.contentProblemRecord = contentProblemRecord; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java new file mode 100644 index 00000000..c14064d5 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java @@ -0,0 +1,51 @@ +package kr.co.morandi.backend.domain.contentrecord; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class ContentRecord extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long contentRecordId; + + private LocalDateTime testDate; + + @ManyToOne(fetch = FetchType.LAZY) + private ContentType contentType; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @Builder.Default + @OneToMany(mappedBy = "contentRecord", cascade = CascadeType.ALL) + private List contentProblemRecords = new ArrayList<>(); + protected abstract ContentProblemRecord createContentProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType); + protected ContentRecord(LocalDateTime testDate, ContentType contentType, Member member, List problems) { + this.testDate = testDate; + this.contentType = contentType; + this.member = member; + this.contentProblemRecords = problems.stream() + .map(problem -> this.createContentProblemRecord(member, problem, this, contentType)) + .toList(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java new file mode 100644 index 00000000..19b4c227 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend.domain.contentrecord.customdefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentproblemrecord.customdefense.CustomDefenseProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("CustomDefenseRecord") +public class CustomDefenseRecord extends ContentRecord { + private Long totalSolvedTime; + private Integer solvedCount; + private Integer problemCount; + @Override + public ContentProblemRecord createContentProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return CustomDefenseProblemRecord.create(member, problem, contentRecord, contentType); + } + private CustomDefenseRecord(CustomDefense customDefense, Member member, LocalDateTime testDate, + List problems) { + super(testDate, customDefense, member, problems); + this.totalSolvedTime = 0L; + this.solvedCount = 0; + this.problemCount = customDefense.getProblemCount(); + } + public static CustomDefenseRecord create(CustomDefense customDefense, Member member, LocalDateTime testDate, + List problems) { + return new CustomDefenseRecord(customDefense, member, testDate, problems); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordRepository.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordRepository.java new file mode 100644 index 00000000..7039f841 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.contentrecord.customdefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDefenseRecordRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java new file mode 100644 index 00000000..fb1d049e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java @@ -0,0 +1,49 @@ +package kr.co.morandi.backend.domain.contentrecord.dailytest; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentproblemrecord.dailytest.DailyTestProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTest; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("DailyTestRecord") +public class DailyTestRecord extends ContentRecord { + private Long solvedCount; + private Integer problemCount; + + private DailyTestRecord(LocalDateTime date, ContentType contentType, + Member member, List problems) { + super(date, contentType, member, problems); + this.solvedCount = 0L; + this.problemCount = problems.size(); + } + @Override + protected ContentProblemRecord createContentProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return DailyTestProblemRecord.create(member, problem, contentRecord, contentType); + } + public static DailyTestRecord create(LocalDateTime date, DailyTest dailyTest, + Member member, List problems) { + + if (Duration.between(dailyTest.getDate(), date).toDays() >= 1) + throw new IllegalArgumentException("오늘의 문제 기록은 출제 시점으로부터 하루 이내에 생성되어야 합니다."); + + return new DailyTestRecord(date, dailyTest, member, problems); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java new file mode 100644 index 00000000..5a162364 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java @@ -0,0 +1,49 @@ +package kr.co.morandi.backend.domain.contentrecord.random; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentproblemrecord.random.RandomDefenseProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("RandomDefenseRecord") +public class RandomDefenseRecord extends ContentRecord { + private Long totalSolvedTime; + private Integer solvedCount; + private Integer problemCount; + + private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; + private static final Integer INITIAL_SOLVED_COUNT = 0; + private RandomDefenseRecord(LocalDateTime testDate, RandomDefense randomDefense, Member member, + List problems) { + super(testDate, randomDefense, member, problems); + this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; + this.solvedCount = INITIAL_SOLVED_COUNT; + this.problemCount = problems.size(); + } + @Override + protected ContentProblemRecord createContentProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return RandomDefenseProblemRecord.create(member, problem, contentRecord, contentType); + } + public static RandomDefenseRecord create(RandomDefense randomDefense, Member member, LocalDateTime testDate, + List problems) { + + return new RandomDefenseRecord(testDate, randomDefense, member, problems); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java new file mode 100644 index 00000000..95d7a610 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java @@ -0,0 +1,46 @@ +package kr.co.morandi.backend.domain.contentrecord.random; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contentproblemrecord.random.StageDefenseProblemRecord; +import kr.co.morandi.backend.domain.contentrecord.ContentRecord; +import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@SuperBuilder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("StageDefenseRecord") +public class StageDefenseRecord extends ContentRecord { + private Long totalSolvedTime; + private Long stageCount; + + private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; + private static final Long INITIAL_STAGE_NUMBER = 1L; + private static final Long INITIAL_STAGE_COUNT = 1L; + private StageDefenseRecord(ContentType contentType, LocalDateTime testDate, + Member member, List problems) { + super(testDate, contentType, member, problems); + this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; + this.stageCount = INITIAL_STAGE_COUNT; + } + @Override + protected ContentProblemRecord createContentProblemRecord(Member member, Problem problem, + ContentRecord contentRecord, ContentType contentType) { + return StageDefenseProblemRecord.create(INITIAL_STAGE_NUMBER, member, problem, contentRecord, contentType); + } + public static StageDefenseRecord create(ContentType contentType, LocalDateTime testDate, + Member member, Problem problem) { + return new StageDefenseRecord(contentType, testDate, member, List.of(problem)); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java index 4138b299..7dfe23c6 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java @@ -19,7 +19,6 @@ public abstract class ContentType extends BaseEntity { private String contentName; private Long attemptCount; - public ContentType(String contentName) { this.contentName = contentName; this.attemptCount = 0L; diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java index 80469aca..483e783d 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java @@ -43,7 +43,8 @@ public class CustomDefense extends ContentType { @OneToMany(mappedBy = "customDefense", cascade = CascadeType.ALL) private List customDefenseProblems = new ArrayList<>(); - private CustomDefense(List problems, Member member, String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { + private CustomDefense(List problems, Member member, String contentName, String description, + Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { super(contentName); this.problemCount = isValidProblemCount(problems.size()); this.timeLimit = isValidTimeLimit(timeLimit); @@ -52,12 +53,11 @@ private CustomDefense(List problems, Member member, String contentName, this.defenseTier = defenseTier; this.member = member; this.customDefenseProblems = problems.stream() - .map(problem -> new CustomDefenseProblems(this, problem)) - .collect(Collectors.toList()); + .map(problem -> CustomDefenseProblems.create(this, problem)) + .toList(); this.createDate = createDate; } - - public static CustomDefense create(List problems, Member member,String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { + public static CustomDefense create(List problems, Member member, String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { return new CustomDefense(problems, member, contentName, description, visibility, defenseTier, timeLimit, createDate); } diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java index 91b1a7ff..8f3e9a24 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java @@ -18,17 +18,17 @@ public class CustomDefenseProblems extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private Problem problem; + private Long submitCount; private Long solvedCount; - public CustomDefenseProblems(CustomDefense customDefense, Problem problem) { + private CustomDefenseProblems(CustomDefense customDefense, Problem problem) { this.customDefense = customDefense; this.problem = problem; this.submitCount = 0L; this.solvedCount = 0L; } - @Builder private CustomDefenseProblems(CustomDefense customDefense, Problem problem, Long submitCount, Long solvedCount) { this.customDefense = customDefense; @@ -36,13 +36,7 @@ private CustomDefenseProblems(CustomDefense customDefense, Problem problem, Long this.submitCount = submitCount; this.solvedCount = solvedCount; } - public static CustomDefenseProblems create(CustomDefense customDefense, Problem problem) { - return CustomDefenseProblems.builder() - .submitCount(0L) - .solvedCount(0L) - .customDefense(customDefense) - .problem(problem) - .build(); + return new CustomDefenseProblems(customDefense, problem); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java index 61da9b99..ee539e29 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java @@ -1,21 +1,44 @@ package kr.co.morandi.backend.domain.contenttype.dailytest; +import jakarta.persistence.CascadeType; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.problem.Problem; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @DiscriminatorValue("DailyTest") @Getter +@SuperBuilder @AllArgsConstructor @NoArgsConstructor public class DailyTest extends ContentType { private LocalDateTime date; + private Integer problemCount; + + @OneToMany(mappedBy = "dailyTest", cascade = CascadeType.ALL) + List dailyTestProblemsList = new ArrayList<>(); + + private DailyTest(LocalDateTime date, String contentName, List problems) { + super(contentName); + this.date = date; + this.dailyTestProblemsList = problems.stream() + .map(problem -> DailyTestProblems.create(this, problem)) + .toList(); + this.problemCount = problems.size(); + } + public static DailyTest create(LocalDateTime date, String contentName, List problems) { + return new DailyTest(date, contentName, problems); + } } diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java index e310c92b..73cb1e91 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java @@ -3,7 +3,10 @@ import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; import kr.co.morandi.backend.domain.problem.Problem; -import lombok.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter @@ -13,21 +16,28 @@ public class DailyTestProblems extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long dailyProblemsId; + private Long submitCount; + + private Long solvedCount; + @ManyToOne(fetch = FetchType.LAZY) private DailyTest dailyTest; @ManyToOne(fetch = FetchType.LAZY) private Problem problem; - private Long submitCount; + private static final Long INITIAL_SUBMIT_COUNT = 0L; - private Long solvedCount; + private static final Long INITIAL_SOLVED_COUNT = 0L; @Builder - private DailyTestProblems(DailyTest dailyTest, Problem problem, Long submitCount, Long solvedCount) { + private DailyTestProblems(DailyTest dailyTest, Problem problem) { this.dailyTest = dailyTest; this.problem = problem; - this.submitCount = submitCount; - this.solvedCount = solvedCount; + this.submitCount = INITIAL_SUBMIT_COUNT; + this.solvedCount = INITIAL_SOLVED_COUNT; + } + public static DailyTestProblems create(DailyTest dailyTest, Problem problem) { + return new DailyTestProblems(dailyTest, problem); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java index 85fa1f11..61caf67e 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java @@ -16,14 +16,12 @@ public class RandomCriteria { private DifficultyRange difficultyRange; private Long minSolvedCount; private Long maxSolvedCount; - @Builder private RandomCriteria(DifficultyRange difficultyRange, Long minSolvedCount, Long maxSolvedCount) { this.difficultyRange = difficultyRange; this.minSolvedCount = minSolvedCount; this.maxSolvedCount = maxSolvedCount; } - public static RandomCriteria of(DifficultyRange difficultyRange, Long minSolvedCount, Long maxSolvedCount) { if (minSolvedCount == null || maxSolvedCount == null) throw new IllegalArgumentException("Solved count must not be null"); @@ -32,14 +30,12 @@ public static RandomCriteria of(DifficultyRange difficultyRange, Long minSolvedC if (minSolvedCount >= maxSolvedCount) throw new IllegalArgumentException("Min solved count must be less than or equal to max solved count"); - return RandomCriteria.builder() .difficultyRange(difficultyRange) .minSolvedCount(minSolvedCount) .maxSolvedCount(maxSolvedCount) .build(); } - @Embeddable @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java index 1da0bb5d..dbdb3770 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java @@ -16,9 +16,28 @@ public class RandomDefense extends ContentType { @Embedded private RandomCriteria randomCriteria; - private Long problemCount; + private Integer problemCount; private Long timeLimit; - - + private RandomDefense(RandomCriteria randomCriteria, Integer problemCount, Long timeLimit, String contentName) { + super(contentName); + this.randomCriteria = randomCriteria; + this.problemCount = isValidProblemCount(problemCount); + this.timeLimit = isValidTimeLimit(timeLimit); + } + public static RandomDefense create(RandomCriteria randomCriteria, Integer problemCount, Long timeLimit, String contentName) { + return new RandomDefense(randomCriteria, problemCount, timeLimit, contentName); + } + private Integer isValidProblemCount(Integer problemCount) { + if (problemCount <= 0) { + throw new IllegalArgumentException("랜덤 디펜스 문제 수는 1문제 이상 이어야 합니다."); + } + return problemCount; + } + private Long isValidTimeLimit(Long timeLimit) { + if (timeLimit <= 0) { + throw new IllegalArgumentException("랜덤 디펜스 제한 시간은 0보다 커야 합니다."); + } + return timeLimit; + } } diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java index 059c21be..304bdaa5 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java @@ -19,5 +19,19 @@ public class RandomStageDefense extends ContentType { private Double averageStage; private Long timeLimit; - + private RandomStageDefense(RandomCriteria randomCriteria, Long timeLimit, String contentName) { + super(contentName); + this.randomCriteria = randomCriteria; + this.averageStage = 0.0; + this.timeLimit = isValidTimeLimit(timeLimit); + } + public static RandomStageDefense create(RandomCriteria randomCriteria, Long timeLimit, String contentName) { + return new RandomStageDefense(randomCriteria, timeLimit, contentName); + } + private Long isValidTimeLimit(Long timeLimit) { + if (timeLimit <= 0) { + throw new IllegalArgumentException("스테이지 모드 제한 시간은 0보다 커야 합니다."); + } + return timeLimit; + } } diff --git a/src/main/java/kr/co/morandi/backend/domain/member/Member.java b/src/main/java/kr/co/morandi/backend/domain/member/Member.java index 97428669..c0638443 100644 --- a/src/main/java/kr/co/morandi/backend/domain/member/Member.java +++ b/src/main/java/kr/co/morandi/backend/domain/member/Member.java @@ -27,8 +27,7 @@ public class Member extends BaseEntity { private String description; @Builder - private Member(Long memberId, String nickname, String baekjoonId, String email, SocialType socialType, String profileImageURL, String description) { - this.memberId = memberId; + private Member(String nickname, String baekjoonId, String email, SocialType socialType, String profileImageURL, String description) { this.nickname = nickname; this.baekjoonId = baekjoonId; this.email = email; diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java new file mode 100644 index 00000000..bf8c9cfd --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java @@ -0,0 +1,84 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.customdefense; + +import kr.co.morandi.backend.domain.contentrecord.customdefense.CustomDefenseRecord; +import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefense; +import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefenseProblems; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.S5; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + + +@ActiveProfiles("test") +class CustomDefenseProblemRecordTest { + + @DisplayName("CustomDefenseProblemRecord를 생성할 수 있다.") + @Test + void create() { + // given + CustomDefense customDefense = createCustomDefense(); + Member member = createMember("member"); + Problem problem = customDefense.getCustomDefenseProblems().stream() + .map(CustomDefenseProblems::getProblem) + .findFirst() + .orElse(null); + + CustomDefenseRecord customDefenseRecord = mock(CustomDefenseRecord.class); + + // when + CustomDefenseProblemRecord customDefenseProblemRecord = CustomDefenseProblemRecord.create(member, problem, customDefenseRecord, customDefense); + + // then + assertThat(customDefenseProblemRecord).isNotNull() + .extracting("member", "problem", "contentType", "contentRecord") + .containsExactly( + member, problem, customDefense, customDefenseRecord + ); + } + @DisplayName("CustomDefenseProblemRecord가 생성됐을 때 solvedTime은 0이다.") + @Test + void initialSolvedTimeIsOne() { + // given + CustomDefense customDefense = createCustomDefense(); + Member member = createMember("member"); + Problem problem = customDefense.getCustomDefenseProblems().stream() + .map(CustomDefenseProblems::getProblem) + .findFirst() + .orElse(null); + + CustomDefenseRecord customDefenseRecord = mock(CustomDefenseRecord.class); + + // when + CustomDefenseProblemRecord customDefenseProblemRecord = CustomDefenseProblemRecord.create(member, problem, customDefenseRecord, customDefense); + + // then + assertThat(customDefenseProblemRecord.getSolvedTime()) + .isEqualTo(0L); + } + private CustomDefense createCustomDefense() { + Member member = createMember("author"); + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + List problems = List.of(problem1, problem2); + + LocalDateTime now = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + return CustomDefense.create(problems, member, "custom_defense", + "custom_defense", OPEN, GOLD, 60L, now); + } + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java new file mode 100644 index 00000000..d228cdf4 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java @@ -0,0 +1,133 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.dailytest; + +import kr.co.morandi.backend.domain.contentrecord.dailytest.DailyTestRecord; +import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTest; +import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTestProblems; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@ActiveProfiles("test") +class DailyTestProblemRecordTest { + @DisplayName("DailyTestProblemRecord를 만들 수 있다.") + @Test + void create() { + // given + DailyTest dailyTest = createDailyTest(); + DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); + Problem problem = dailyTest.getDailyTestProblemsList().stream() + .map(DailyTestProblems::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); + + // then + assertThat(dailyTestProblemRecord).isNotNull() + .extracting("member", "problem", "contentType", "contentRecord") + .contains(member, problem, dailyTest, dailyTestRecord); + } + @DisplayName("DailyTestProblemRecord가 생성되면 isSolved는 false이다") + @Test + void initialIsSolvedFalse() { + // given + DailyTest dailyTest = createDailyTest(); + DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); + Problem problem = dailyTest.getDailyTestProblemsList().stream() + .map(DailyTestProblems::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); + + // then + assertThat(dailyTestProblemRecord.getIsSolved()).isFalse(); + } + @DisplayName("DailyTestProblemRecord가 생성되면 submitCount는 0이다") + @Test + void initialSubmitCountIsZero() { + // given + DailyTest dailyTest = createDailyTest(); + DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); + Problem problem = dailyTest.getDailyTestProblemsList().stream() + .map(DailyTestProblems::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); + + // then + assertThat(dailyTestProblemRecord.getSubmitCount()).isZero(); + } + @DisplayName("DailyTestProblemRecord가 생성되면 solvedCode는 null이다") + @Test + void initialSolvedCodeIsSetToNull() { + // given + DailyTest dailyTest = createDailyTest(); + DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); + Problem problem = dailyTest.getDailyTestProblemsList().stream() + .map(DailyTestProblems::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); + + // then + assertThat(dailyTestProblemRecord.getSolvedCode()) + .isEqualTo(null); + } + + private DailyTest createDailyTest() { + LocalDateTime createDate = LocalDateTime.of(2023, 3, 5, 0, 0); + return DailyTest.create(createDate, "3월 5일 문제", createProblem()); + } + private List createProblem() { + return List.of( + Problem.builder() + .baekjoonProblemId(1L) + .problemTier(B5) + .problemStatus(ACTIVE) + .solvedCount(0L) + .build(), + Problem.builder() + .baekjoonProblemId(2L) + .problemTier(B5) + .problemStatus(ACTIVE) + .solvedCount(0L) + .build(), + Problem.builder() + .baekjoonProblemId(3L) + .problemTier(B5) + .problemStatus(ACTIVE) + .solvedCount(0L) + .build() + ); + } + private Member createMember() { + return Member.builder() + .email("user" + "@gmail.com") + .socialType(GOOGLE) + .nickname("nickname") + .description("description") + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecordTest.java new file mode 100644 index 00000000..e8e6e157 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecordTest.java @@ -0,0 +1,104 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.random; + +import kr.co.morandi.backend.domain.contentrecord.random.RandomDefenseRecord; +import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@ActiveProfiles("test") +class RandomDefenseProblemRecordTest { + @DisplayName("RandomDefenseProblemRecord를 생성한다.") + @Test + void create() { + // given + RandomDefense randomDefense = mock(RandomDefense.class); + RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember("test"); + + // when + RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + + // then + assertThat(randomDefenseProblemRecord).isNotNull() + .extracting("member", "problem", "contentType", "contentRecord") + .contains(member, problem, randomDefense, randomDefenseRecord); + + } + @DisplayName("RandomDefenseProblemRecord가 생성되면 solvedTime은 0이다") + @Test + void initialSolvedTimeIsZero() { + // given + RandomDefense randomDefense = mock(RandomDefense.class); + RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember("test"); + + // when + RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + + // then + assertThat(randomDefenseProblemRecord.getSolvedTime()).isZero(); + } + @DisplayName("RandomDefenseProblemRecord가 생성되면 isSolved는 false이다") + @Test + void initialIsSolvedFalse() { + // given + RandomDefense randomDefense = mock(RandomDefense.class); + RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember("test"); + + // when + RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + + // then + assertThat(randomDefenseProblemRecord.getIsSolved()).isFalse(); + } + @DisplayName("RandomDefenseProblemRecord가 생성되면 submitCount는 0이다") + @Test + void initialSubmitCountIsZero() { + // given + RandomDefense randomDefense = mock(RandomDefense.class); + RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember("test"); + + // when + RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + + // then + assertThat(randomDefenseProblemRecord.getSubmitCount()).isZero(); + } + @DisplayName("RandomDefenseProblemRecord가 생성되면 solvedCode는 null이다") + @Test + void initialSolvedCodeIsSetToNull() { + // given + RandomDefense randomDefense = mock(RandomDefense.class); + RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember("test"); + + // when + RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + + // then + assertThat(randomDefenseProblemRecord.getSolvedCode()) + .isEqualTo(null); + } + + private Problem createProblem() { + return Problem.create(1L, B5, 0L); + } + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecordTest.java new file mode 100644 index 00000000..f4fbb5dc --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecordTest.java @@ -0,0 +1,138 @@ +package kr.co.morandi.backend.domain.contentproblemrecord.random; + +import kr.co.morandi.backend.domain.contentrecord.random.StageDefenseRecord; +import kr.co.morandi.backend.domain.contenttype.random.randomstagedefense.RandomStageDefense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@ActiveProfiles("test") +class StageDefenseProblemRecordTest { + @DisplayName("스테이지 문제 기록이 생성되면 초기 정답 시간은 0이다.") + @Test + void initialSolvedTimeIsZero() { + // given + RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); + StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember(); + + // when + StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + + // then + assertThat(stageDefenseProblemRecord.getSolvedTime()).isEqualTo(0L); + + } + @DisplayName("원하는 스테이지 번호에 따른 스테이지 문제 기록을 만들 수 있다.") + @Test + void createStageDefenseProblemRecordWithStageNumber() { + // given + RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); + StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember(); + + // when + StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + + // then + assertThat(stageDefenseProblemRecord.getStageNumber()).isEqualTo(1L); + + } + @DisplayName("스테이지 문제 기록이 생성되면 초기 정답 여부는 false이다.") + @Test + void initialIsSolvedIsFalse() { + // given + RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); + StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember(); + + // when + StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + + // then + assertThat(stageDefenseProblemRecord) + .extracting("isSolved") + .isEqualTo(false); + + } + @DisplayName("스테이지 문제 기록이 생성되면 submitCount는 0이다.") + @Test + void initialSubmitCountIsZero() { + // given + RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); + StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember(); + + // when + StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + + // then + assertThat(stageDefenseProblemRecord) + .extracting("submitCount") + .isEqualTo(0L); + + } + @DisplayName("스테이지 문제 기록이 생성되면 초기 정답 코드는 null이다.") + @Test + void initialSolvedCodeIsNull() { + // given + RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); + StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember(); + + // when + StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + + // then + assertThat(stageDefenseProblemRecord) + .extracting("solvedCode") + .isEqualTo(null); + + } + @DisplayName("스테이지 문제 기록을 생성할 수 있다.") + @Test + void createStageDefenseProblemRecord() { + // given + RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); + StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + Problem problem = createProblem(); + Member member = createMember(); + + // when + StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + + // then + assertThat(stageDefenseProblemRecord) + .extracting("member", "problem", "contentRecord", "contentType") + .contains(member, problem, stageDefenseRecord, randomStageDefense); + } + private Member createMember() { + return Member.builder() + .email("user" + "@gmail.com") + .socialType(GOOGLE) + .nickname("nickname") + .description("description") + .build(); + } + private Problem createProblem() { + return Problem.builder() + .baekjoonProblemId(1L) + .problemTier(B5) + .problemStatus(ACTIVE) + .solvedCount(0L) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordTest.java new file mode 100644 index 00000000..e5a15860 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordTest.java @@ -0,0 +1,194 @@ +package kr.co.morandi.backend.domain.contentrecord.customdefense; + +import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefense; +import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefenseProblems; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.S5; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class CustomDefenseRecordTest { + @DisplayName("커스텀 디펜스 기록이 만들어 졌을 때 시험 날짜는 시작한 시점과 같아야 한다.") + @Test + void testDateIsEqualNow() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + + Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // then + assertThat(customDefenseRecord.getTestDate()).isEqualTo(startTime); + } + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 맞춘 문제 수는 0으로 설정되어야 한다.") + @Test + void solvedCountIsZero() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + + Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // then + assertThat(customDefenseRecord.getSolvedCount()).isZero(); + } + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 총 문제 수 기록은 커스텀 디펜스 문제 수와 같아야 한다.") + @Test + void problemCountIsEqual() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + + Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // then + assertThat(customDefenseRecord.getProblemCount()).isEqualTo(customDefense.getProblemCount()); + } + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 초기 전체 소요 시간은 0분 이어야 한다.") + @Test + void totalSolvedTimeIsZero() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + + Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // then + assertThat(customDefenseRecord.getTotalSolvedTime()).isZero(); + } + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 세부 문제 기록의 정답 여부는 모두 오답이어야 한다.") + @Test + void isSolvedFalse() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + + Member member = createMember("user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // then + assertThat(customDefenseRecord.getContentProblemRecords()) + .extracting("isSolved") + .containsExactlyInAnyOrder( + false, + false + ); + } + + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 세부 문제 기록의 문제 기록의 소요 시간은 0분이어야 한다.") + @Test + void solvedTimeIsZero() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + Member member = createMember("user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // then + assertThat(customDefenseRecord.getContentProblemRecords()) + .extracting("solvedTime") + .containsExactlyInAnyOrder( + 0L, + 0L + ); + } + + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 세부 문제 기록의 제출 횟수는 0회 이어야 한다.") + @Test + void submitCountIsZero() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + Member member = createMember("user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // when & then + assertThat(customDefenseRecord.getContentProblemRecords()) + .extracting("submitCount") + .containsExactlyInAnyOrder( + 0L, + 0L + ); + } + + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 세부 문제 기록의 문제 정답 코드는 null 값 이어야 한다.") + @Test + void solvedCodeIsNull() { + // given + CustomDefense customDefense = createCustomDefense(); + List problems = getCustomDefenseProblems(customDefense); + Member member = createMember("user"); + LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + // when + CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + + // when & then + assertThat(customDefenseRecord.getContentProblemRecords()) + .extracting("solvedCode") + .containsExactlyInAnyOrder( + null, + null + ); + + } + + private CustomDefense createCustomDefense() { + Member member = createMember("author"); + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + List problems = List.of(problem1, problem2); + + LocalDateTime now = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); + + return CustomDefense.create(problems, member, "custom_defense", + "custom_defense", OPEN, GOLD, 60L, now); + } + private List getCustomDefenseProblems(CustomDefense customDefense) { + List customDefenseProblems = customDefense.getCustomDefenseProblems(); + + return customDefenseProblems.stream() + .map(CustomDefenseProblems::getProblem) + .toList(); + } + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecordTest.java new file mode 100644 index 00000000..7ae1cae1 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecordTest.java @@ -0,0 +1,173 @@ +package kr.co.morandi.backend.domain.contentrecord.dailytest; + +import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTest; +import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTestProblems; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.groups.Tuple.tuple; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ActiveProfiles("test") +class DailyTestRecordTest { + @DisplayName("오늘의 문제 기록이 만들어졌을 때 푼 문제 수는 0문제 이어야 한다.") + @Test + void solvedCountIsZero() { + // given + DailyTest dailyTest = createDailyTest(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + List problems = getProblemList(dailyTest); + + // when + DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + + // then + assertThat(dailyTestRecord.getSolvedCount()).isZero(); + } + @DisplayName("오늘의 문제 기록이 만들어졌을 때 전체 문제 수는 오늘의 문제에 출제된 문제 들과 같아야 한다.") + @Test + void problemCountIsEqual() { + // given + DailyTest dailyTest = createDailyTest(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + List problems = getProblemList(dailyTest); + + // when + DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + + // then + assertThat(dailyTestRecord.getProblemCount()).isEqualTo(dailyTest.getProblemCount()); + assertThat(dailyTestRecord.getContentProblemRecords()) + .extracting("problem", "contentRecord") + .containsExactlyInAnyOrder( + tuple(problems.get(0), dailyTestRecord), + tuple(problems.get(1), dailyTestRecord), + tuple(problems.get(2), dailyTestRecord) + ); + } + @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가면 예외가 발생한다.") + @Test + void recordCreateExceptionWhenOverOneDay() { + // given + LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + DailyTest dailyTest = createDailyTest(createdTime); + + Member member = createMember("user"); + List problems = getProblemList(dailyTest); + + LocalDateTime startTime = LocalDateTime.of(2024, 3, 2, 0, 0, 0); + + // when & then + assertThatThrownBy(() -> DailyTestRecord.create(startTime, dailyTest, member, problems)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("오늘의 문제 기록은 출제 시점으로부터 하루 이내에 생성되어야 합니다."); + } + @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가지 않으면 정상적으로 등록된다.") + @Test + void recordCreatedWithinOneDay() { + // given + LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + DailyTest dailyTest = createDailyTest(createdTime); + + Member member = createMember("user"); + List problems = getProblemList(dailyTest); + + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 23, 59, 59); + + // when + DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + + // then + assertNotNull(dailyTestRecord); + } + @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 여부는 모두 오답 상태여야 한다.") + @Test + void isSolvedIsFalse() { + // given + DailyTest dailyTest = createDailyTest(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + List problems = getProblemList(dailyTest); + + // when + DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + List contentProblemRecords = dailyTestRecord.getContentProblemRecords(); + + // then + assertThat(contentProblemRecords) + .extracting("isSolved") + .containsExactlyInAnyOrder(false, false, false); + } + @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 제출 횟수는 모두 0회여야 한다.") + @Test + void submitCountIsZero() { + // given + DailyTest dailyTest = createDailyTest(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + List problems = getProblemList(dailyTest); + + // when + DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + List contentProblemRecords = dailyTestRecord.getContentProblemRecords(); + + // then + assertThat(contentProblemRecords) + .extracting("submitCount") + .containsExactlyInAnyOrder(0L, 0L, 0L); + } + @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 코드는 null 이어야 한다.") + @Test + void solvedCodeIsNull() { + // given + DailyTest dailyTest = createDailyTest(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + List problems = getProblemList(dailyTest); + + // when + DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime , dailyTest, member, problems); + List contentProblemRecords = dailyTestRecord.getContentProblemRecords(); + + // then + assertThat(contentProblemRecords) + .extracting("solvedCode") + .containsExactlyInAnyOrder(null, null, null); + } + private List getProblemList(DailyTest dailyTest) { + return dailyTest.getDailyTestProblemsList().stream() + .map(DailyTestProblems::getProblem) + .toList(); + } + private DailyTest createDailyTest() { + List problems = createProblems(); + LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + return DailyTest.create(createdTime, "오늘의 문제 테스트", problems); + } + private DailyTest createDailyTest(LocalDateTime createdTime) { + List problems = createProblems(); + return DailyTest.create(createdTime, "오늘의 문제 테스트", problems); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return List.of(problem1, problem2, problem3); + } + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecordTest.java new file mode 100644 index 00000000..f8e088ea --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecordTest.java @@ -0,0 +1,193 @@ +package kr.co.morandi.backend.domain.contentrecord.random; + +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class RandomDefenseRecordTest { + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 맞춘 문제수는 0문제 이어야 한다.") + @Test + void solvedCountIsZero() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getSolvedCount()).isZero(); + } + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 총 문제 수는 랜덤 디펜스 문제 개수와 같아야 한다.") + @Test + void problemCountIsEqual() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getProblemCount()).isEqualTo(randomDefense.getProblemCount()); + } + + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 전체 소요 시간은 0분 이어야 한다.") + @Test + void totalSolvedTimeIsZero() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getTotalSolvedTime()).isZero(); + } + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 시점과 랜덤 디펜스 시험 날짜는 같아야 한다.") + @Test + void testDateIsEqualTestDate() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getTestDate()).isEqualTo(now); + } + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 세부 문제 기록의 정답 여부는 모두 오답이어야 한다.") + @Test + void isSolvedIsFalse() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getContentProblemRecords()) + .extracting("isSolved") + .containsExactly( + false, + false, + false, + false + ); + } + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 세부 문제 기록의 제출 횟수는 모두 0회여야 한다.") + @Test + void submitCountIsZero() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getContentProblemRecords()) + .extracting("submitCount") + .containsExactly( + 0L, + 0L, + 0L, + 0L + ); + } + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 세부 문제 기록의 정답 코드는 모두 null 이어야 한다.") + @Test + void solvedCodeIsNull() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord + = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getContentProblemRecords()) + .extracting("solvedCode") + .containsExactly( + null, + null, + null, + null + ); + } + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 세부 문제 기록의 소요 시간은 모두 0분 이어야 한다.") + @Test + void solvedTimeIsZero() { + // given + RandomDefense randomDefense = createRandomDefense(); + List problems = getProblemsByRandom(); + Member member = createMember("user"); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); + + // when + RandomDefenseRecord randomDefenseRecord + = RandomDefenseRecord.create(randomDefense, member, now, problems); + + // then + assertThat(randomDefenseRecord.getContentProblemRecords()) + .extracting("solvedTime") + .containsExactly( + 0L, + 0L, + 0L, + 0L + ); + } + private RandomDefense createRandomDefense() { + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + return RandomDefense.create(randomCriteria, 4, 120L, "브론즈 랜덤 디펜스"); + } + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } + private List getProblemsByRandom() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + Problem problem4 = Problem.create(4L, P5, 0L); + return List.of(problem1, problem2, problem3, problem4); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecordTest.java new file mode 100644 index 00000000..ec50bbcc --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecordTest.java @@ -0,0 +1,166 @@ +package kr.co.morandi.backend.domain.contentrecord.random; + +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.contenttype.random.randomstagedefense.RandomStageDefense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class StageDefenseRecordTest { + @DisplayName("스테이지 기록이 만들어졌을 때 포함된 문제 수는 1개다.") + @Test + void stageCountIsOne() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + + // then + assertThat(stageDefenseRecord.getStageCount()).isOne(); + } + @DisplayName("스테이지 기록이 만들어졌을 때 만들어진 첫번째 문제 기록의 스테이지 번호는 1번이어야 한다.") + @Test + void initialStageNumberIsSetToOne() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + + // then + assertThat(stageDefenseRecord.getContentProblemRecords()) + .extracting("stageNumber") + .containsExactly(1L); + } + @DisplayName("스테이지 기록이 만들어졌을 때 전체 소요 시간은 0분 이어야 한다.") + @Test + void totalSolvedTimeIsZero() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + + // then + assertThat(stageDefenseRecord.getTotalSolvedTime()).isZero(); + } + @DisplayName("스테이지 기록이 만들어졌을 때 시험 날짜는 시작한 시점과 같아야 한다.") + @Test + void testDateEqualNow() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + + // then + assertThat(stageDefenseRecord.getTestDate()).isEqualTo(startTime); + } + @DisplayName("스테이지 기록이 만들어졌을 때 만들어진 첫번째 문제 기록의 소요 시간은 0분 이어야 한다.") + @Test + void solvedTimeIsZero() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + + // then + assertThat(stageDefenseRecord.getContentProblemRecords()) + .extracting("solvedTime") + .containsExactly(0L); + } + @DisplayName("스테이지 기록이 만들어졌을 때 만들어진 첫번째 문제 기록의 정답 여부는 오답이어야 한다.") + @Test + void isSolvedIsFalse() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + + // then + assertThat(stageDefenseRecord.getContentProblemRecords()) + .extracting("isSolved") + .containsExactly(false); + } + @DisplayName("스테이지 기록이 만들어졌을 때 만들어진 첫번째 문제 기록의 제출 횟수는 0회 이어야한다.") + @Test + void submitCountIsZero() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + + // then + assertThat(stageDefenseRecord.getContentProblemRecords()) + .extracting("submitCount") + .containsExactly(0L); + } + @DisplayName("스테이지 기록이 만들어졌을 때 만들어진 첫번째 문제 기록의 정답 코드는 null 이어야 한다.") + @Test + void solvedCodeIsNull() { + // given + RandomStageDefense randomStageDefense = createRandomStageDefense(); + + Problem problem = Problem.create(1L, B5, 100L); + LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); + Member member = createMember("user"); + + // when + StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + + // then + assertThat(stageDefenseRecord.getContentProblemRecords()) + .extracting("solvedCode") + .containsExactly((String)null); + } + private RandomStageDefense createRandomStageDefense() { + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + return RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + } + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java index 516134b7..6a2dd31a 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java @@ -21,38 +21,17 @@ import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.*; -@SpringBootTest @ActiveProfiles("test") class CustomDefenseTest { - - @Autowired - private ProblemRepository problemRepository; - - @Autowired - private MemberRepository memberRepository; - - - @Autowired - private CustomDefenseProblemsRepository customDefenseProblemsRepository; - - @AfterEach - void tearDown() { - customDefenseProblemsRepository.deleteAllInBatch(); - problemRepository.deleteAllInBatch(); - memberRepository.deleteAllInBatch(); - } - @DisplayName("커스텀 디펜스를 생성하면 등록 시간을 기록한다.") @Test void registeredWithDateTime() { // given Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); - memberRepository.save(member); Problem problem1 = Problem.create(1L, B5, 0L); Problem problem2 = Problem.create(2L, S5, 0L); List problems = List.of(problem1, problem2); - problemRepository.saveAll(problems); LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); @@ -63,18 +42,15 @@ void registeredWithDateTime() { assertThat(customDefense.getCreateDate()).isEqualTo(now); } - @DisplayName("커스텀 디펜스를 생성하면 컨텐츠 이름과 설명을 기록한다.") @Test void createCustomDefenseWithContentName() { // given Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); - memberRepository.save(member); Problem problem1 = Problem.create(1L, B5, 0L); Problem problem2 = Problem.create(2L, S5, 0L); List problems = List.of(problem1, problem2); - problemRepository.saveAll(problems); LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); @@ -93,13 +69,11 @@ void createCustomDefenseWithContentName() { void createCustomDefenseProblemCount() { // given Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); - memberRepository.save(member); List problems = createProblems(); LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); - // when CustomDefense customDefense = CustomDefense.create(problems, member, "커스텀 디펜스1", "커스텀 디펜스1 설명", OPEN, GOLD, 60L, now); @@ -113,14 +87,11 @@ void createCustomDefenseProblemCount() { tuple(3L, G5) ); } - - @DisplayName("커스텀 디펜스를 빈 문제 리스트로 생성하면 예외가 발생한다") @Test void createCustomDefenseWithoutProblem() { // given Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); - memberRepository.save(member); List problems = Collections.emptyList(); @@ -138,7 +109,6 @@ void createCustomDefenseWithoutProblem() { void createCustomDefenseWithZeroTimeLimit() { // given Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); - memberRepository.save(member); List problems = createProblems(); @@ -155,7 +125,6 @@ void createCustomDefenseWithZeroTimeLimit() { void createCustomDefenseWithNegativeTimeLimit() { // given Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); - memberRepository.save(member); List problems = createProblems(); @@ -172,9 +141,7 @@ private List createProblems() { Problem problem1 = Problem.create(1L, B5, 0L); Problem problem2 = Problem.create(2L, S5, 0L); Problem problem3 = Problem.create(3L, G5, 0L); - List problems = List.of(problem1, problem2, problem3); - problemRepository.saveAll(problems); - return problems; + return List.of(problem1, problem2, problem3); } } \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsTest.java new file mode 100644 index 00000000..95513d38 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsTest.java @@ -0,0 +1,51 @@ +package kr.co.morandi.backend.domain.contenttype.dailytest; + +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class DailyTestProblemsTest { + @DisplayName("오늘의 문제가 만들어졌을 때, 초기의 문제 제출횟수는 0이어야 한다.") + @Test + void submitCountIsZero() { + // given + List problems = createProblems(); + LocalDateTime now = LocalDateTime.now(); + DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + List dailyTestProblemsList = dailyTest.getDailyTestProblemsList(); + + // when & then + assertThat(dailyTestProblemsList) + .extracting("submitCount") + .containsExactlyInAnyOrder(0L, 0L, 0L); + } + + @DisplayName("오늘의 문제가 만들어졌을 때, 초기 정답자 수는 0이어야 한다.") + @Test + void solvedCountIsZero() { + // given + List problems = createProblems(); + LocalDateTime now = LocalDateTime.now(); + DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + List dailyTestProblemsList = dailyTest.getDailyTestProblemsList(); + + // when & then + assertThat(dailyTestProblemsList) + .extracting("solvedCount") + .containsExactlyInAnyOrder(0L, 0L, 0L); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return List.of(problem1, problem2, problem3); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestTest.java new file mode 100644 index 00000000..f031cb1e --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestTest.java @@ -0,0 +1,61 @@ +package kr.co.morandi.backend.domain.contenttype.dailytest; + +import kr.co.morandi.backend.domain.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class DailyTestTest { + @DisplayName("오늘의 문제 세트가 만들어진 시점에서 시도한 사람의 수는 0명 이어야 한다.") + @Test + void attemptCountIsZero() { + // given + List problems = createProblems(); + LocalDateTime now = LocalDateTime.now(); + + // when + DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + + // then + assertThat(dailyTest.getAttemptCount()).isZero(); + } + @DisplayName("오늘의 문제가 만들어진 시점에 등록된 날짜는 만들어진 시점과 같아야 한다.") + @Test + void testDateEqualNow() { + // given + List problems = createProblems(); + LocalDateTime now = LocalDateTime.now(); + + // when + DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + + // then + assertThat(dailyTest.getDate()).isEqualTo(now); + } + @DisplayName("오늘의 문제가 만들어진 이름은 일치해야한다.") + @Test + void contentNameIsEqual() { + // given + List problems = createProblems(); + LocalDateTime now = LocalDateTime.now(); + + // when + DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + + // then + assertThat(dailyTest.getContentName()).isEqualTo("오늘의 문제 테스트"); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return List.of(problem1, problem2, problem3); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseTest.java new file mode 100644 index 00000000..e878a8a7 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseTest.java @@ -0,0 +1,56 @@ +package kr.co.morandi.backend.domain.contenttype.random.randomdefense; + +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ActiveProfiles("test") +class RandomDefenseTest { + @DisplayName("랜덤 디펜스를 생성할 때 등록한 정보가 올바르게 저장되어야 한다.") + @Test + void createRandomDefense() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + + // when + RandomDefense randomDefense = RandomDefense.create(randomCriteria, 4, 120L, "브론즈 랜덤 디펜스"); + + // then + assertThat(randomDefense) + .extracting("randomCriteria.DifficultyRange.startDifficulty", "randomCriteria.DifficultyRange.endDifficulty", + "problemCount", "timeLimit", "contentName", "RandomCriteria.minSolvedCount", "RandomCriteria.maxSolvedCount") + .containsExactly(B5, B1, 4, 120L, "브론즈 랜덤 디펜스", 100L, 200L); + } + + @DisplayName("랜덤 디펜스를 생성할 때 시간 제한이 0분 이하로 설정되면 예외가 발생한다.") + @Test + void timeLimitGreatherThanZero() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + + // when & then + assertThatThrownBy(() -> RandomDefense.create(randomCriteria, 4, 0L, "브론즈 랜덤 디펜스")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("랜덤 디펜스 제한 시간은 0보다 커야 합니다."); + } + @DisplayName("랜덤 디펜스를 생성할 때 문제 개수가 0개 이하로 설정되면 예외가 발생한다.") + @Test + void problemCountGreatherThanZero() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + + // when & then + assertThatThrownBy(() -> RandomDefense.create(randomCriteria, 0, 120L, "브론즈 랜덤 디펜스")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("랜덤 디펜스 문제 수는 1문제 이상 이어야 합니다."); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseTest.java new file mode 100644 index 00000000..78865818 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseTest.java @@ -0,0 +1,70 @@ +package kr.co.morandi.backend.domain.contenttype.random.randomstagedefense; + +import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ActiveProfiles("test") +class RandomStageDefenseTest { + @DisplayName("스테이지 모드를 처음 만들 때 정보가 올바르게 저장되어야 한다.") + @Test + void createRamdomStageDefense() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + + // when + RandomStageDefense randomStageDefense = RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + + // then + assertThat(randomStageDefense) + .extracting("randomCriteria.minSolvedCount", "randomCriteria.maxSolvedCount", "timeLimit", + "randomCriteria.difficultyRange.startDifficulty", "randomCriteria.difficultyRange.endDifficulty", + "contentName") + .containsExactly(100L, 200L, 120L, B5, B1, "브론즈 스테이지 모드"); + } + @DisplayName("스테이지 모드를 처음 만들 때 평균 스테이지 수는 0으로 설정되어 있어야 한다.") + @Test + void averageStageIsZero() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + + // when + RandomStageDefense randomStageDefense = RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + + // then + assertThat(randomStageDefense.getAverageStage()).isZero(); + } + @DisplayName("스테이지 모드를 처음 만들 때 시도한 사람 수는 0명 이어야 한다.") + @Test + void attemptCountIsZero() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + + // when + RandomStageDefense randomStageDefense = RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + + // then + assertThat(randomStageDefense.getAttemptCount()).isZero(); + } + @DisplayName("스테이지 모드를 처음 만들 때 시간 제한은 0분 미만일 경우 예외가 발생한다.") + @Test + void timeLimitGreatherThanZero() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + + // when & then + assertThatThrownBy(() -> RandomStageDefense.create(randomCriteria, 0L, "브론즈 스테이지 모드")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("스테이지 모드 제한 시간은 0보다 커야 합니다."); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java index ad2cba87..a510bf6a 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java @@ -23,12 +23,10 @@ class RandomDefenseRepositoryTest { @Autowired private RandomDefenseRepository randomDefenseRepository; - @AfterEach void tearDown() { randomDefenseRepository.deleteAllInBatch(); } - @Test @DisplayName("저장된 랜덤 디펜스의 정보를 조회할 수 있다.") void findByContentName() { @@ -42,9 +40,8 @@ void findByContentName() { // then assertThat(findRandomDefense) .extracting("randomCriteria.maxSolvedCount", "randomCriteria.minSolvedCount", "timeLimit", "problemCount", "randomCriteria.difficultyRange.startDifficulty", "randomCriteria.difficultyRange.endDifficulty") - .containsExactly(200L, 100L, 1000L, 4L, B5, B1); + .containsExactly(200L, 100L, 1000L, 4, B5, B1); } - @DisplayName("랜덤 디펜스들을 모두 조회하여 가져올 수 있다.") @Test void findAllRandomDefense(){ @@ -59,12 +56,11 @@ void findAllRandomDefense(){ assertThat(findRandomDefenses).hasSize(3) .extracting("randomCriteria.maxSolvedCount", "randomCriteria.minSolvedCount", "timeLimit", "problemCount", "randomCriteria.difficultyRange.startDifficulty", "randomCriteria.difficultyRange.endDifficulty") .containsExactlyInAnyOrder( - tuple(200L, 100L, 1000L, 4L, B5, B1), - tuple(200L, 100L, 1000L, 4L, S5, S1), - tuple(200L, 100L, 1000L, 4L, G5, G1) + tuple(200L, 100L, 1000L, 4, B5, B1), + tuple(200L, 100L, 1000L, 4, S5, S1), + tuple(200L, 100L, 1000L, 4, G5, G1) ); } - private List createRandomDefense() { RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); RandomCriteria.DifficultyRange silverRange = RandomCriteria.DifficultyRange.of(S5, S1); @@ -72,21 +68,21 @@ private List createRandomDefense() { RandomDefense bronzeDefense = RandomDefense.builder() .timeLimit(1000L) - .problemCount(4L) + .problemCount(4) .contentName("브론즈 랜덤 디펜스") .randomCriteria(RandomCriteria.of(bronzeRange,100L,200L)) .build(); RandomDefense silverDefense = RandomDefense.builder() .timeLimit(1000L) - .problemCount(4L) + .problemCount(4) .contentName("실버 랜덤 디펜스") .randomCriteria(RandomCriteria.of(silverRange,100L,200L)) .build(); RandomDefense goldDefense = RandomDefense.builder() .timeLimit(1000L) - .problemCount(4L) + .problemCount(4) .contentName("골드 랜덤 디펜스") .randomCriteria(RandomCriteria.of(goldRange,100L,200L)) .build(); From 8cda6b7a6ad4634d2bebeaa8120f4b3b23fc9d65 Mon Sep 17 00:00:00 2001 From: Jeong Yong Choi <51875177+aj4941@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:53:33 +0900 Subject: [PATCH 07/14] =?UTF-8?q?=EC=BB=A8=ED=85=90=EC=B8=A0=EC=99=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=EB=90=9C=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :pencil2: ContentType을 Defense로 변경 * :pencil2: Content 관련 테이블명 수정 * :pencil2: 테이블 관련 테스트 코드 수정 --- .../backend/domain/bookmark/BookMark.java | 8 +- .../ContentMemberLikes.java | 8 +- .../CustomDefenseProblemRecord.java | 34 ----- .../CustomDefenseProblemRecordRepository.java | 6 - .../dailytest/DailyTestProblemRecord.java | 28 ---- .../random/RandomDefenseProblemRecord.java | 35 ----- .../random/StageDefenseProblemRecord.java | 34 ----- .../domain/contentrecord/ContentRecord.java | 51 ------- .../customdefense/CustomDefenseRecord.java | 44 ------ .../dailytest/DailyTestRecord.java | 49 ------- .../random/RandomDefenseRecord.java | 49 ------- .../random/StageDefenseRecord.java | 46 ------ .../CustomDefenseProblemsRepository.java | 6 - .../contenttype/dailytest/DailyTest.java | 44 ------ .../DailyTestProblemsRepository.java | 6 - .../dailytest/DailyTestRepository.java | 7 - .../RandomStageDefenseRepository.java | 6 - .../ContentType.java => defense/Defense.java} | 8 +- .../customdefense/CustomDefense.java | 11 +- .../customdefense/CustomDefenseProblem.java} | 12 +- .../CustomDefenseProblemRepository.java | 6 + .../CustomDefenseRepository.java | 2 +- .../customdefense/DefenseTier.java | 2 +- .../customdefense/Visibility.java | 2 +- .../defense/dailydefense/DailyDefense.java | 44 ++++++ .../dailydefense/DailyDefenseProblem.java} | 16 +-- .../DailyDefenseProblemRepository.java | 6 + .../dailydefense/DailyDefenseRepository.java | 7 + .../random}/RandomDefense.java | 8 +- .../random}/RandomDefenseRepository.java | 2 +- .../random/randomcriteria/RandomCriteria.java | 4 +- .../stagedefense/StageDefense.java} | 14 +- .../stagedefense/StageDefenseRepository.java | 6 + .../tier/ProblemTier.java | 2 +- .../Detail.java} | 22 +-- .../detail/customdefense/CustomDetail.java | 33 +++++ .../customdefense/CustomDetailRepository.java | 6 + .../detail/dailydefense/DailyDetail.java | 27 ++++ .../detail/randomdefense/RandomDetail.java | 35 +++++ .../detail/stagedefense/StageDetail.java | 34 +++++ .../submit/SubmitCode.java | 10 +- .../backend/domain/problem/Problem.java | 3 +- .../morandi/backend/domain/record/Record.java | 51 +++++++ .../CustomDefenseRecordRepository.java | 4 +- .../record/customdefense/CustomRecord.java | 44 ++++++ .../record/dailydefense/DailyRecord.java | 48 +++++++ .../record/randomdefense/RandomRecord.java | 48 +++++++ .../record/stagedefense/StageRecord.java | 46 ++++++ .../dailytest/DailyTestProblemRecordTest.java | 133 ------------------ .../CustomDefenseRepositoryTest.java | 12 +- .../customdefense/CustomDefenseTest.java | 13 +- .../DailyDefenseProblemTest.java} | 18 +-- .../dailydefense/DailyDefenseTest.java} | 18 +-- .../randomcriteria/DifficultyRangeTest.java | 10 +- .../randomcriteria/RandomCriteriaTest.java | 6 +- .../RandomDefenseRepositoryTest.java | 12 +- .../randomdefense/RandomDefenseTest.java | 9 +- .../stagedefense/StageDefenseTest.java} | 19 +-- .../customdefense/CustomDetailTest.java} | 32 ++--- .../detail/dailydefense/DailyDetailTest.java | 133 ++++++++++++++++++ .../randomdefense/RandomDetailTest.java} | 33 +++-- .../randomdefense/StageDetailTest.java} | 52 ++++--- .../domain/problem/ProblemRepositoryTest.java | 2 +- .../backend/domain/problem/ProblemTest.java | 2 +- .../customdefense/CustomRecordTest.java} | 44 +++--- .../dailydefense/DailyRecordTest.java} | 92 ++++++------ .../randomdefense/RandomRecordTest.java} | 36 +++-- .../randomdefense/StageRecordTest.java} | 59 ++++---- 68 files changed, 873 insertions(+), 886 deletions(-) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java rename src/main/java/kr/co/morandi/backend/domain/{contenttype/ContentType.java => defense/Defense.java} (73%) rename src/main/java/kr/co/morandi/backend/domain/{contenttype => defense}/customdefense/CustomDefense.java (86%) rename src/main/java/kr/co/morandi/backend/domain/{contenttype/customdefense/CustomDefenseProblems.java => defense/customdefense/CustomDefenseProblem.java} (63%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblemRepository.java rename src/main/java/kr/co/morandi/backend/domain/{contenttype => defense}/customdefense/CustomDefenseRepository.java (79%) rename src/main/java/kr/co/morandi/backend/domain/{contenttype => defense}/customdefense/DefenseTier.java (79%) rename src/main/java/kr/co/morandi/backend/domain/{contenttype => defense}/customdefense/Visibility.java (75%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java rename src/main/java/kr/co/morandi/backend/domain/{contenttype/dailytest/DailyTestProblems.java => defense/dailydefense/DailyDefenseProblem.java} (63%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java rename src/main/java/kr/co/morandi/backend/domain/{contenttype/random/randomdefense => defense/random}/RandomDefense.java (83%) rename src/main/java/kr/co/morandi/backend/domain/{contenttype/random/randomdefense => defense/random}/RandomDefenseRepository.java (68%) rename src/main/java/kr/co/morandi/backend/domain/{contenttype => defense}/random/randomcriteria/RandomCriteria.java (94%) rename src/main/java/kr/co/morandi/backend/domain/{contenttype/random/randomstagedefense/RandomStageDefense.java => defense/stagedefense/StageDefense.java} (56%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java rename src/main/java/kr/co/morandi/backend/domain/{contenttype => defense}/tier/ProblemTier.java (87%) rename src/main/java/kr/co/morandi/backend/domain/{contentproblemrecord/ContentProblemRecord.java => detail/Detail.java} (65%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetail.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetail.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/StageDetail.java rename src/main/java/kr/co/morandi/backend/domain/{contentproblemrecord => detail}/submit/SubmitCode.java (62%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/record/Record.java rename src/main/java/kr/co/morandi/backend/domain/{contentrecord => record}/customdefense/CustomDefenseRecordRepository.java (56%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/record/stagedefense/StageRecord.java delete mode 100644 src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java rename src/test/java/kr/co/morandi/backend/domain/{contenttype => defense}/customdefense/CustomDefenseRepositoryTest.java (86%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype => defense}/customdefense/CustomDefenseTest.java (90%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype/dailytest/DailyTestProblemsTest.java => defense/dailydefense/DailyDefenseProblemTest.java} (67%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype/dailytest/DailyTestTest.java => defense/dailydefense/DailyDefenseTest.java} (69%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype => defense}/randomcriteria/DifficultyRangeTest.java (83%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype => defense}/randomcriteria/RandomCriteriaTest.java (91%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype => defense}/randomdefense/RandomDefenseRepositoryTest.java (88%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype/random => defense}/randomdefense/RandomDefenseTest.java (87%) rename src/test/java/kr/co/morandi/backend/domain/{contenttype/random/randomstagedefense/RandomStageDefenseTest.java => defense/stagedefense/StageDefenseTest.java} (75%) rename src/test/java/kr/co/morandi/backend/domain/{contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java => detail/customdefense/CustomDetailTest.java} (64%) create mode 100644 src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java rename src/test/java/kr/co/morandi/backend/domain/{contentproblemrecord/random/RandomDefenseProblemRecordTest.java => detail/randomdefense/RandomDetailTest.java} (63%) rename src/test/java/kr/co/morandi/backend/domain/{contentproblemrecord/random/StageDefenseProblemRecordTest.java => detail/randomdefense/StageDetailTest.java} (59%) rename src/test/java/kr/co/morandi/backend/domain/{contentrecord/customdefense/CustomDefenseRecordTest.java => record/customdefense/CustomRecordTest.java} (76%) rename src/test/java/kr/co/morandi/backend/domain/{contentrecord/dailytest/DailyTestRecordTest.java => record/dailydefense/DailyRecordTest.java} (59%) rename src/test/java/kr/co/morandi/backend/domain/{contentrecord/random/RandomDefenseRecordTest.java => record/randomdefense/RandomRecordTest.java} (79%) rename src/test/java/kr/co/morandi/backend/domain/{contentrecord/random/StageDefenseRecordTest.java => record/randomdefense/StageRecordTest.java} (64%) diff --git a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java b/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java index 44063a42..6bfc9f3d 100644 --- a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java +++ b/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java @@ -2,7 +2,7 @@ import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.defense.Defense; import kr.co.morandi.backend.domain.member.Member; import lombok.*; @@ -14,14 +14,14 @@ public class BookMark extends BaseEntity { private Long bookMarkId; @ManyToOne(fetch = FetchType.LAZY) - private ContentType contentType; + private Defense defense; @ManyToOne(fetch = FetchType.LAZY) private Member member; @Builder - private BookMark(ContentType contentType, Member member) { - this.contentType = contentType; + private BookMark(Defense defense, Member member) { + this.defense = defense; this.member = member; } } diff --git a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java index 54def21f..225010e0 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java +++ b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java @@ -2,7 +2,7 @@ import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.defense.Defense; import kr.co.morandi.backend.domain.member.Member; import lombok.AccessLevel; import lombok.Builder; @@ -18,11 +18,11 @@ public class ContentMemberLikes extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private Member member; @ManyToOne(fetch = FetchType.LAZY) - private ContentType contentType; + private Defense defense; @Builder - private ContentMemberLikes(Member member, ContentType contentType) { + private ContentMemberLikes(Member member, Defense defense) { this.member = member; - this.contentType = contentType; + this.defense = defense; } } diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java deleted file mode 100644 index 88703e54..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecord.java +++ /dev/null @@ -1,34 +0,0 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.customdefense; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -@Entity -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@DiscriminatorValue("CustomDefenseProblemRecord") -public class CustomDefenseProblemRecord extends ContentProblemRecord { - - private Long solvedTime; - - private static final long INITIAL_SOLVED_TIME = 0L; - private CustomDefenseProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - super(member, problem, contentRecord, contentType); - this.solvedTime = INITIAL_SOLVED_TIME; - } - public static CustomDefenseProblemRecord create(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return new CustomDefenseProblemRecord(member, problem, contentRecord, contentType); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java deleted file mode 100644 index 810ca4c2..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.customdefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CustomDefenseProblemRecordRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java deleted file mode 100644 index daecf75d..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecord.java +++ /dev/null @@ -1,28 +0,0 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.dailytest; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -@Entity -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DiscriminatorValue("DailyTestProblemRecord") -public class DailyTestProblemRecord extends ContentProblemRecord { - - private DailyTestProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - super(member, problem, contentRecord, contentType); - } - public static DailyTestProblemRecord create(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return new DailyTestProblemRecord(member, problem, contentRecord, contentType); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java deleted file mode 100644 index f934ba6d..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecord.java +++ /dev/null @@ -1,35 +0,0 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.random; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -@Entity -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@DiscriminatorValue("RandomDefenseProblemRecord") -public class RandomDefenseProblemRecord extends ContentProblemRecord { - - private Long solvedTime; - - private static final long INITIAL_SOLVED_TIME = 0L; - private RandomDefenseProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - super(member, problem, contentRecord, contentType); - this.solvedTime = INITIAL_SOLVED_TIME; - } - - public static RandomDefenseProblemRecord create(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return new RandomDefenseProblemRecord(member, problem, contentRecord, contentType); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java deleted file mode 100644 index 9896a994..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecord.java +++ /dev/null @@ -1,34 +0,0 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.random; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -@Entity -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@DiscriminatorValue("StageDefenseProblemRecord") -public class StageDefenseProblemRecord extends ContentProblemRecord { - private Long solvedTime; - private Long stageNumber; - - private StageDefenseProblemRecord(Long stageNumber, Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - super(member, problem, contentRecord, contentType); - this.solvedTime = 0L; - this.stageNumber = stageNumber; - } - public static StageDefenseProblemRecord create(Long stageNumber, Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return new StageDefenseProblemRecord(stageNumber, member, problem, contentRecord, contentType); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java deleted file mode 100644 index c14064d5..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentrecord/ContentRecord.java +++ /dev/null @@ -1,51 +0,0 @@ -package kr.co.morandi.backend.domain.contentrecord; - -import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Entity -@Inheritance(strategy = InheritanceType.JOINED) -@DiscriminatorColumn -@Getter -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class ContentRecord extends BaseEntity { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long contentRecordId; - - private LocalDateTime testDate; - - @ManyToOne(fetch = FetchType.LAZY) - private ContentType contentType; - - @ManyToOne(fetch = FetchType.LAZY) - private Member member; - - @Builder.Default - @OneToMany(mappedBy = "contentRecord", cascade = CascadeType.ALL) - private List contentProblemRecords = new ArrayList<>(); - protected abstract ContentProblemRecord createContentProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType); - protected ContentRecord(LocalDateTime testDate, ContentType contentType, Member member, List problems) { - this.testDate = testDate; - this.contentType = contentType; - this.member = member; - this.contentProblemRecords = problems.stream() - .map(problem -> this.createContentProblemRecord(member, problem, this, contentType)) - .toList(); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java deleted file mode 100644 index 19b4c227..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecord.java +++ /dev/null @@ -1,44 +0,0 @@ -package kr.co.morandi.backend.domain.contentrecord.customdefense; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentproblemrecord.customdefense.CustomDefenseProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; -import java.time.LocalDateTime; -import java.util.List; - -@Entity -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@DiscriminatorValue("CustomDefenseRecord") -public class CustomDefenseRecord extends ContentRecord { - private Long totalSolvedTime; - private Integer solvedCount; - private Integer problemCount; - @Override - public ContentProblemRecord createContentProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return CustomDefenseProblemRecord.create(member, problem, contentRecord, contentType); - } - private CustomDefenseRecord(CustomDefense customDefense, Member member, LocalDateTime testDate, - List problems) { - super(testDate, customDefense, member, problems); - this.totalSolvedTime = 0L; - this.solvedCount = 0; - this.problemCount = customDefense.getProblemCount(); - } - public static CustomDefenseRecord create(CustomDefense customDefense, Member member, LocalDateTime testDate, - List problems) { - return new CustomDefenseRecord(customDefense, member, testDate, problems); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java deleted file mode 100644 index fb1d049e..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecord.java +++ /dev/null @@ -1,49 +0,0 @@ -package kr.co.morandi.backend.domain.contentrecord.dailytest; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentproblemrecord.dailytest.DailyTestProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTest; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.List; - -@Entity -@Getter -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DiscriminatorValue("DailyTestRecord") -public class DailyTestRecord extends ContentRecord { - private Long solvedCount; - private Integer problemCount; - - private DailyTestRecord(LocalDateTime date, ContentType contentType, - Member member, List problems) { - super(date, contentType, member, problems); - this.solvedCount = 0L; - this.problemCount = problems.size(); - } - @Override - protected ContentProblemRecord createContentProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return DailyTestProblemRecord.create(member, problem, contentRecord, contentType); - } - public static DailyTestRecord create(LocalDateTime date, DailyTest dailyTest, - Member member, List problems) { - - if (Duration.between(dailyTest.getDate(), date).toDays() >= 1) - throw new IllegalArgumentException("오늘의 문제 기록은 출제 시점으로부터 하루 이내에 생성되어야 합니다."); - - return new DailyTestRecord(date, dailyTest, member, problems); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java deleted file mode 100644 index 5a162364..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecord.java +++ /dev/null @@ -1,49 +0,0 @@ -package kr.co.morandi.backend.domain.contentrecord.random; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentproblemrecord.random.RandomDefenseProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.LocalDateTime; -import java.util.List; - -@Entity -@Getter -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DiscriminatorValue("RandomDefenseRecord") -public class RandomDefenseRecord extends ContentRecord { - private Long totalSolvedTime; - private Integer solvedCount; - private Integer problemCount; - - private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; - private static final Integer INITIAL_SOLVED_COUNT = 0; - private RandomDefenseRecord(LocalDateTime testDate, RandomDefense randomDefense, Member member, - List problems) { - super(testDate, randomDefense, member, problems); - this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; - this.solvedCount = INITIAL_SOLVED_COUNT; - this.problemCount = problems.size(); - } - @Override - protected ContentProblemRecord createContentProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return RandomDefenseProblemRecord.create(member, problem, contentRecord, contentType); - } - public static RandomDefenseRecord create(RandomDefense randomDefense, Member member, LocalDateTime testDate, - List problems) { - - return new RandomDefenseRecord(testDate, randomDefense, member, problems); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java b/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java deleted file mode 100644 index 95d7a610..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecord.java +++ /dev/null @@ -1,46 +0,0 @@ -package kr.co.morandi.backend.domain.contentrecord.random; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contentproblemrecord.random.StageDefenseProblemRecord; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.LocalDateTime; -import java.util.List; - -@Entity -@SuperBuilder -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DiscriminatorValue("StageDefenseRecord") -public class StageDefenseRecord extends ContentRecord { - private Long totalSolvedTime; - private Long stageCount; - - private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; - private static final Long INITIAL_STAGE_NUMBER = 1L; - private static final Long INITIAL_STAGE_COUNT = 1L; - private StageDefenseRecord(ContentType contentType, LocalDateTime testDate, - Member member, List problems) { - super(testDate, contentType, member, problems); - this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; - this.stageCount = INITIAL_STAGE_COUNT; - } - @Override - protected ContentProblemRecord createContentProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { - return StageDefenseProblemRecord.create(INITIAL_STAGE_NUMBER, member, problem, contentRecord, contentType); - } - public static StageDefenseRecord create(ContentType contentType, LocalDateTime testDate, - Member member, Problem problem) { - return new StageDefenseRecord(contentType, testDate, member, List.of(problem)); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java deleted file mode 100644 index 349be994..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblemsRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CustomDefenseProblemsRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java deleted file mode 100644 index ee539e29..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package kr.co.morandi.backend.domain.contenttype.dailytest; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.OneToMany; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Entity -@DiscriminatorValue("DailyTest") -@Getter -@SuperBuilder -@AllArgsConstructor -@NoArgsConstructor -public class DailyTest extends ContentType { - - private LocalDateTime date; - - private Integer problemCount; - - @OneToMany(mappedBy = "dailyTest", cascade = CascadeType.ALL) - List dailyTestProblemsList = new ArrayList<>(); - - private DailyTest(LocalDateTime date, String contentName, List problems) { - super(contentName); - this.date = date; - this.dailyTestProblemsList = problems.stream() - .map(problem -> DailyTestProblems.create(this, problem)) - .toList(); - this.problemCount = problems.size(); - } - public static DailyTest create(LocalDateTime date, String contentName, List problems) { - return new DailyTest(date, contentName, problems); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java deleted file mode 100644 index 3b96eee0..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.contenttype.dailytest; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DailyTestProblemsRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java deleted file mode 100644 index 6f896bca..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.domain.contenttype.dailytest; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DailyTestRepository extends JpaRepository { - -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java deleted file mode 100644 index 986a0db1..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.contenttype.random.randomstagedefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RandomStageDefenseRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java b/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java similarity index 73% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java rename to src/main/java/kr/co/morandi/backend/domain/defense/Defense.java index 7dfe23c6..cd7ac2ca 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/ContentType.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype; +package kr.co.morandi.backend.domain.defense; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; @@ -11,15 +11,15 @@ @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class ContentType extends BaseEntity { +public abstract class Defense extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long contentTypeId; + private Long defenseId; private String contentName; private Long attemptCount; - public ContentType(String contentName) { + public Defense(String contentName) { this.contentName = contentName; this.attemptCount = 0L; } diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefense.java similarity index 86% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefense.java index 483e783d..7f49566e 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefense.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.defense.Defense; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; import lombok.AccessLevel; @@ -13,14 +13,13 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; @Entity @DiscriminatorValue("CustomDefense") @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class CustomDefense extends ContentType { +public class CustomDefense extends Defense { private LocalDateTime createDate; @@ -41,7 +40,7 @@ public class CustomDefense extends ContentType { @Builder.Default @OneToMany(mappedBy = "customDefense", cascade = CascadeType.ALL) - private List customDefenseProblems = new ArrayList<>(); + private List customDefenseProblems = new ArrayList<>(); private CustomDefense(List problems, Member member, String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { @@ -53,7 +52,7 @@ private CustomDefense(List problems, Member member, String contentName, this.defenseTier = defenseTier; this.member = member; this.customDefenseProblems = problems.stream() - .map(problem -> CustomDefenseProblems.create(this, problem)) + .map(problem -> CustomDefenseProblem.create(this, problem)) .toList(); this.createDate = createDate; } diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblem.java similarity index 63% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblem.java index 8f3e9a24..055145db 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseProblems.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblem.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; @@ -8,7 +8,7 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class CustomDefenseProblems extends BaseEntity { +public class CustomDefenseProblem extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long customProblemsId; @@ -23,20 +23,20 @@ public class CustomDefenseProblems extends BaseEntity { private Long solvedCount; - private CustomDefenseProblems(CustomDefense customDefense, Problem problem) { + private CustomDefenseProblem(CustomDefense customDefense, Problem problem) { this.customDefense = customDefense; this.problem = problem; this.submitCount = 0L; this.solvedCount = 0L; } @Builder - private CustomDefenseProblems(CustomDefense customDefense, Problem problem, Long submitCount, Long solvedCount) { + private CustomDefenseProblem(CustomDefense customDefense, Problem problem, Long submitCount, Long solvedCount) { this.customDefense = customDefense; this.problem = problem; this.submitCount = submitCount; this.solvedCount = solvedCount; } - public static CustomDefenseProblems create(CustomDefense customDefense, Problem problem) { - return new CustomDefenseProblems(customDefense, problem); + public static CustomDefenseProblem create(CustomDefense customDefense, Problem problem) { + return new CustomDefenseProblem(customDefense, problem); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblemRepository.java new file mode 100644 index 00000000..e2ed7a84 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblemRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.defense.customdefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDefenseProblemRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepository.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepository.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepository.java index eadcd8e2..9981593c 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepository.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepository.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/DefenseTier.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/DefenseTier.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/DefenseTier.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/DefenseTier.java index 0c5030c6..8b427a89 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/DefenseTier.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/DefenseTier.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/Visibility.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/Visibility.java similarity index 75% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/Visibility.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/Visibility.java index 7e80c06d..6e4f831f 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/customdefense/Visibility.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/Visibility.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java new file mode 100644 index 00000000..88f9a5ca --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend.domain.defense.dailydefense; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@DiscriminatorValue("DailyDefense") +@Getter +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +public class DailyDefense extends Defense { + + private LocalDateTime date; + + private Integer problemCount; + + @OneToMany(mappedBy = "DailyDefense", cascade = CascadeType.ALL) + List DailyDefenseProblems = new ArrayList<>(); + + private DailyDefense(LocalDateTime date, String contentName, List problems) { + super(contentName); + this.date = date; + this.DailyDefenseProblems = problems.stream() + .map(problem -> DailyDefenseProblem.create(this, problem)) + .toList(); + this.problemCount = problems.size(); + } + public static DailyDefense create(LocalDateTime date, String contentName, List problems) { + return new DailyDefense(date, contentName, problems); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblem.java similarity index 63% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java rename to src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblem.java index 73cb1e91..49b215fb 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblems.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblem.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.dailytest; +package kr.co.morandi.backend.domain.defense.dailydefense; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; @@ -11,17 +11,17 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class DailyTestProblems extends BaseEntity { +public class DailyDefenseProblem extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long dailyProblemsId; + private Long dailyDefenseProblemId; private Long submitCount; private Long solvedCount; @ManyToOne(fetch = FetchType.LAZY) - private DailyTest dailyTest; + private DailyDefense DailyDefense; @ManyToOne(fetch = FetchType.LAZY) private Problem problem; @@ -31,13 +31,13 @@ public class DailyTestProblems extends BaseEntity { private static final Long INITIAL_SOLVED_COUNT = 0L; @Builder - private DailyTestProblems(DailyTest dailyTest, Problem problem) { - this.dailyTest = dailyTest; + private DailyDefenseProblem(DailyDefense DailyDefense, Problem problem) { + this.DailyDefense = DailyDefense; this.problem = problem; this.submitCount = INITIAL_SUBMIT_COUNT; this.solvedCount = INITIAL_SOLVED_COUNT; } - public static DailyTestProblems create(DailyTest dailyTest, Problem problem) { - return new DailyTestProblems(dailyTest, problem); + public static DailyDefenseProblem create(DailyDefense DailyDefense, Problem problem) { + return new DailyDefenseProblem(DailyDefense, problem); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java new file mode 100644 index 00000000..368475ac --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.defense.dailydefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DailyDefenseProblemRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java new file mode 100644 index 00000000..9837eea4 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.domain.defense.dailydefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DailyDefenseRepository extends JpaRepository { + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefense.java similarity index 83% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java rename to src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefense.java index dbdb3770..c7ac007b 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefense.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.contenttype.random.randomdefense; +package kr.co.morandi.backend.domain.defense.random; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; import lombok.*; import lombok.experimental.SuperBuilder; @@ -11,7 +11,7 @@ @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class RandomDefense extends ContentType { +public class RandomDefense extends Defense { @Embedded private RandomCriteria randomCriteria; diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefenseRepository.java similarity index 68% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseRepository.java rename to src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefenseRepository.java index 1ee74496..53014441 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseRepository.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefenseRepository.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.random.randomdefense; +package kr.co.morandi.backend.domain.defense.random; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java b/src/main/java/kr/co/morandi/backend/domain/defense/random/randomcriteria/RandomCriteria.java similarity index 94% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java rename to src/main/java/kr/co/morandi/backend/domain/defense/random/randomcriteria/RandomCriteria.java index 61caf67e..602f91b0 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomcriteria/RandomCriteria.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/random/randomcriteria/RandomCriteria.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.contenttype.random.randomcriteria; +package kr.co.morandi.backend.domain.defense.random.randomcriteria; import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; -import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.tier.ProblemTier; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefense.java similarity index 56% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java rename to src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefense.java index 304bdaa5..fa4fcdc7 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefense.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.contenttype.random.randomstagedefense; +package kr.co.morandi.backend.domain.defense.stagedefense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.contenttype.ContentType; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; import lombok.*; import lombok.experimental.SuperBuilder; @@ -11,7 +11,7 @@ @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class RandomStageDefense extends ContentType { +public class StageDefense extends Defense { @Embedded private RandomCriteria randomCriteria; @@ -19,14 +19,14 @@ public class RandomStageDefense extends ContentType { private Double averageStage; private Long timeLimit; - private RandomStageDefense(RandomCriteria randomCriteria, Long timeLimit, String contentName) { + private StageDefense(RandomCriteria randomCriteria, Long timeLimit, String contentName) { super(contentName); this.randomCriteria = randomCriteria; this.averageStage = 0.0; this.timeLimit = isValidTimeLimit(timeLimit); } - public static RandomStageDefense create(RandomCriteria randomCriteria, Long timeLimit, String contentName) { - return new RandomStageDefense(randomCriteria, timeLimit, contentName); + public static StageDefense create(RandomCriteria randomCriteria, Long timeLimit, String contentName) { + return new StageDefense(randomCriteria, timeLimit, contentName); } private Long isValidTimeLimit(Long timeLimit) { if (timeLimit <= 0) { diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java new file mode 100644 index 00000000..318b27cb --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.defense.stagedefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StageDefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contenttype/tier/ProblemTier.java b/src/main/java/kr/co/morandi/backend/domain/defense/tier/ProblemTier.java similarity index 87% rename from src/main/java/kr/co/morandi/backend/domain/contenttype/tier/ProblemTier.java rename to src/main/java/kr/co/morandi/backend/domain/defense/tier/ProblemTier.java index 0a9d5c46..86d4ab31 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contenttype/tier/ProblemTier.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/tier/ProblemTier.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.tier; +package kr.co.morandi.backend.domain.defense.tier; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/ContentProblemRecord.java b/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java similarity index 65% rename from src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/ContentProblemRecord.java rename to src/main/java/kr/co/morandi/backend/domain/detail/Detail.java index ae5a61de..9333eb5a 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/ContentProblemRecord.java +++ b/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.contentproblemrecord; +package kr.co.morandi.backend.domain.detail; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.contentrecord.ContentRecord; -import kr.co.morandi.backend.domain.contenttype.ContentType; +import kr.co.morandi.backend.domain.defense.Defense; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -17,10 +17,10 @@ @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class ContentProblemRecord extends BaseEntity { +public abstract class Detail extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long contentProblemRecordId; + private Long detailId; private Boolean isSolved; @@ -29,10 +29,10 @@ public abstract class ContentProblemRecord extends BaseEntity { private String solvedCode; @ManyToOne(fetch = FetchType.LAZY) - private ContentType contentType; + private Defense defense; @ManyToOne(fetch = FetchType.LAZY) - private ContentRecord contentRecord; + private Record record; @ManyToOne(fetch = FetchType.LAZY) private Member member; @@ -43,13 +43,13 @@ public abstract class ContentProblemRecord extends BaseEntity { private static final Long INITIAL_SUBMIT_COUNT = 0L; private static final Boolean INITIAL_IS_SOLVED = false; - protected ContentProblemRecord(Member member, Problem problem, - ContentRecord contentRecord, ContentType contentType) { + protected Detail(Member member, Problem problem, + Record record, Defense defense) { this.isSolved = INITIAL_IS_SOLVED; this.submitCount = INITIAL_SUBMIT_COUNT; this.solvedCode = null; - this.contentType = contentType; - this.contentRecord = contentRecord; + this.defense = defense; + this.record = record; this.member = member; this.problem = problem; } diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetail.java new file mode 100644 index 00000000..4d02888b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetail.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.domain.detail.customdefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("CustomDefenseProblemRecord") +public class CustomDetail extends Detail { + + private Long solvedTime; + + private static final long INITIAL_SOLVED_TIME = 0L; + private CustomDetail(Member member, Problem problem, Record record, Defense defense) { + super(member, problem, record, defense); + this.solvedTime = INITIAL_SOLVED_TIME; + } + public static CustomDetail create(Member member, Problem problem, + Record record, Defense defense) { + return new CustomDetail(member, problem, record, defense); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java b/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java new file mode 100644 index 00000000..7fae3b37 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.detail.customdefense; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDetailRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java new file mode 100644 index 00000000..d18d436c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.domain.detail.dailydefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("DailyDefenseProblemRecord") +public class DailyDetail extends Detail { + + private DailyDetail(Member member, Problem problem, Record record, Defense defense) { + super(member, problem, record, defense); + } + public static DailyDetail create(Member member, Problem problem, + Record record, Defense defense) { + return new DailyDetail(member, problem, record, defense); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetail.java new file mode 100644 index 00000000..03c04e03 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetail.java @@ -0,0 +1,35 @@ +package kr.co.morandi.backend.domain.detail.randomdefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("RandomDefenseProblemRecord") +public class RandomDetail extends Detail { + + private Long solvedTime; + + private static final long INITIAL_SOLVED_TIME = 0L; + private RandomDetail(Member member, Problem problem, + Record record, Defense defense) { + super(member, problem, record, defense); + this.solvedTime = INITIAL_SOLVED_TIME; + } + + public static RandomDetail create(Member member, Problem problem, + Record record, Defense defense) { + return new RandomDetail(member, problem, record, defense); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/StageDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/StageDetail.java new file mode 100644 index 00000000..fadc3485 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/StageDetail.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.domain.detail.stagedefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("StageDefenseProblemRecord") +public class StageDetail extends Detail { + private Long solvedTime; + private Long stageNumber; + + private StageDetail(Long stageNumber, Member member, Problem problem, + Record record, Defense defense) { + super(member, problem, record, defense); + this.solvedTime = 0L; + this.stageNumber = stageNumber; + } + public static StageDetail create(Long stageNumber, Member member, Problem problem, + Record record, Defense defense) { + return new StageDetail(stageNumber, member, problem, record, defense); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/submit/SubmitCode.java b/src/main/java/kr/co/morandi/backend/domain/detail/submit/SubmitCode.java similarity index 62% rename from src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/submit/SubmitCode.java rename to src/main/java/kr/co/morandi/backend/domain/detail/submit/SubmitCode.java index c4159298..2c188195 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contentproblemrecord/submit/SubmitCode.java +++ b/src/main/java/kr/co/morandi/backend/domain/detail/submit/SubmitCode.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.submit; +package kr.co.morandi.backend.domain.detail.submit; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; +import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.member.Member; import lombok.AccessLevel; import lombok.Builder; @@ -22,12 +22,12 @@ public class SubmitCode { private Member member; @ManyToOne(fetch = FetchType.LAZY) - private ContentProblemRecord contentProblemRecord; + private Detail detail; @Builder - private SubmitCode(String submitCodeLink, Member member, ContentProblemRecord contentProblemRecord) { + private SubmitCode(String submitCodeLink, Member member, Detail detail) { this.submitCodeLink = submitCodeLink; this.member = member; - this.contentProblemRecord = contentProblemRecord; + this.detail = detail; } } diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java b/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java index 384146ce..5667cd95 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java +++ b/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java @@ -2,10 +2,9 @@ import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.tier.ProblemTier; import lombok.*; -import static kr.co.morandi.backend.domain.problem.ProblemStatus.HOLD; import static kr.co.morandi.backend.domain.problem.ProblemStatus.INIT; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/record/Record.java b/src/main/java/kr/co/morandi/backend/domain/record/Record.java new file mode 100644 index 00000000..f3351800 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/record/Record.java @@ -0,0 +1,51 @@ +package kr.co.morandi.backend.domain.record; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class Record extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long detailId; + + private LocalDateTime testDate; + + @ManyToOne(fetch = FetchType.LAZY) + private Defense defense; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @Builder.Default + @OneToMany(mappedBy = "record", cascade = CascadeType.ALL) + private List details = new ArrayList<>(); + protected abstract Detail createDetail(Member member, Problem problem, + Record record, Defense defense); + protected Record(LocalDateTime testDate, Defense defense, Member member, List problems) { + this.testDate = testDate; + this.defense = defense; + this.member = member; + this.details = problems.stream() + .map(problem -> this.createDetail(member, problem, this, defense)) + .toList(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordRepository.java b/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomDefenseRecordRepository.java similarity index 56% rename from src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordRepository.java rename to src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomDefenseRecordRepository.java index 7039f841..0bab1c85 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordRepository.java +++ b/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomDefenseRecordRepository.java @@ -1,6 +1,6 @@ -package kr.co.morandi.backend.domain.contentrecord.customdefense; +package kr.co.morandi.backend.domain.record.customdefense; import org.springframework.data.jpa.repository.JpaRepository; -public interface CustomDefenseRecordRepository extends JpaRepository { +public interface CustomDefenseRecordRepository extends JpaRepository { } diff --git a/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecord.java new file mode 100644 index 00000000..ecca6d25 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecord.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend.domain.record.customdefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.customdefense.CustomDefense; +import kr.co.morandi.backend.domain.detail.customdefense.CustomDetail; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@DiscriminatorValue("CustomRecord") +public class CustomRecord extends Record { + private Long totalSolvedTime; + private Integer solvedCount; + private Integer problemCount; + @Override + public Detail createDetail(Member member, Problem problem, + Record record, Defense defense) { + return CustomDetail.create(member, problem, record, defense); + } + private CustomRecord(CustomDefense customDefense, Member member, LocalDateTime testDate, + List problems) { + super(testDate, customDefense, member, problems); + this.totalSolvedTime = 0L; + this.solvedCount = 0; + this.problemCount = customDefense.getProblemCount(); + } + public static CustomRecord create(CustomDefense customDefense, Member member, LocalDateTime testDate, + List problems) { + return new CustomRecord(customDefense, member, testDate, problems); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java new file mode 100644 index 00000000..9786e6f9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java @@ -0,0 +1,48 @@ +package kr.co.morandi.backend.domain.record.dailydefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefense; +import kr.co.morandi.backend.domain.detail.dailydefense.DailyDetail; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("DailyDefenseRecord") +public class DailyRecord extends Record { + private Long solvedCount; + private Integer problemCount; + + private DailyRecord(LocalDateTime date, Defense defense, + Member member, List problems) { + super(date, defense, member, problems); + this.solvedCount = 0L; + this.problemCount = problems.size(); + } + @Override + protected Detail createDetail(Member member, Problem problem, Record record, Defense defense) { + return DailyDetail.create(member, problem, record, defense); + } + public static DailyRecord create(LocalDateTime date, DailyDefense DailyDefense, + Member member, List problems) { + + if (Duration.between(DailyDefense.getDate(), date).toDays() >= 1) + throw new IllegalArgumentException("오늘의 문제 기록은 출제 시점으로부터 하루 이내에 생성되어야 합니다."); + + return new DailyRecord(date, DailyDefense, member, problems); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecord.java new file mode 100644 index 00000000..76a29c6e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecord.java @@ -0,0 +1,48 @@ +package kr.co.morandi.backend.domain.record.randomdefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.random.RandomDefense; +import kr.co.morandi.backend.domain.detail.randomdefense.RandomDetail; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("RandomDefenseRecord") +public class RandomRecord extends Record { + private Long totalSolvedTime; + private Integer solvedCount; + private Integer problemCount; + + private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; + private static final Integer INITIAL_SOLVED_COUNT = 0; + private RandomRecord(LocalDateTime testDate, RandomDefense randomDefense, Member member, + List problems) { + super(testDate, randomDefense, member, problems); + this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; + this.solvedCount = INITIAL_SOLVED_COUNT; + this.problemCount = problems.size(); + } + @Override + protected Detail createDetail(Member member, Problem problem, Record record, Defense defense) { + return RandomDetail.create(member, problem, record, defense); + } + public static RandomRecord create(RandomDefense randomDefense, Member member, LocalDateTime testDate, + List problems) { + + return new RandomRecord(testDate, randomDefense, member, problems); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/record/stagedefense/StageRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/stagedefense/StageRecord.java new file mode 100644 index 00000000..6a84a54e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/record/stagedefense/StageRecord.java @@ -0,0 +1,46 @@ +package kr.co.morandi.backend.domain.record.stagedefense; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.detail.stagedefense.StageDetail; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.List; + +@Entity +@SuperBuilder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("StageDefenseRecord") +public class StageRecord extends Record { + private Long totalSolvedTime; + private Long stageCount; + + private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; + private static final Long INITIAL_STAGE_NUMBER = 1L; + private static final Long INITIAL_STAGE_COUNT = 1L; + private StageRecord(Defense defense, LocalDateTime testDate, + Member member, List problems) { + super(testDate, defense, member, problems); + this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; + this.stageCount = INITIAL_STAGE_COUNT; + } + @Override + protected Detail createDetail(Member member, Problem problem, + Record record, Defense defense) { + return StageDetail.create(INITIAL_STAGE_NUMBER, member, problem, record, defense); + } + public static StageRecord create(Defense defense, LocalDateTime testDate, + Member member, Problem problem) { + return new StageRecord(defense, testDate, member, List.of(problem)); + } +} diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java deleted file mode 100644 index d228cdf4..00000000 --- a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/dailytest/DailyTestProblemRecordTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.dailytest; - -import kr.co.morandi.backend.domain.contentrecord.dailytest.DailyTestRecord; -import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTest; -import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTestProblems; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.test.context.ActiveProfiles; - -import java.time.LocalDateTime; -import java.util.List; - -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; -import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -@ActiveProfiles("test") -class DailyTestProblemRecordTest { - @DisplayName("DailyTestProblemRecord를 만들 수 있다.") - @Test - void create() { - // given - DailyTest dailyTest = createDailyTest(); - DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); - Problem problem = dailyTest.getDailyTestProblemsList().stream() - .map(DailyTestProblems::getProblem) - .findFirst() - .orElse(null); - Member member = createMember(); - - // when - DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); - - // then - assertThat(dailyTestProblemRecord).isNotNull() - .extracting("member", "problem", "contentType", "contentRecord") - .contains(member, problem, dailyTest, dailyTestRecord); - } - @DisplayName("DailyTestProblemRecord가 생성되면 isSolved는 false이다") - @Test - void initialIsSolvedFalse() { - // given - DailyTest dailyTest = createDailyTest(); - DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); - Problem problem = dailyTest.getDailyTestProblemsList().stream() - .map(DailyTestProblems::getProblem) - .findFirst() - .orElse(null); - Member member = createMember(); - - // when - DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); - - // then - assertThat(dailyTestProblemRecord.getIsSolved()).isFalse(); - } - @DisplayName("DailyTestProblemRecord가 생성되면 submitCount는 0이다") - @Test - void initialSubmitCountIsZero() { - // given - DailyTest dailyTest = createDailyTest(); - DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); - Problem problem = dailyTest.getDailyTestProblemsList().stream() - .map(DailyTestProblems::getProblem) - .findFirst() - .orElse(null); - Member member = createMember(); - - // when - DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); - - // then - assertThat(dailyTestProblemRecord.getSubmitCount()).isZero(); - } - @DisplayName("DailyTestProblemRecord가 생성되면 solvedCode는 null이다") - @Test - void initialSolvedCodeIsSetToNull() { - // given - DailyTest dailyTest = createDailyTest(); - DailyTestRecord dailyTestRecord = mock(DailyTestRecord.class); - Problem problem = dailyTest.getDailyTestProblemsList().stream() - .map(DailyTestProblems::getProblem) - .findFirst() - .orElse(null); - Member member = createMember(); - - // when - DailyTestProblemRecord dailyTestProblemRecord = DailyTestProblemRecord.create(member, problem, dailyTestRecord, dailyTest); - - // then - assertThat(dailyTestProblemRecord.getSolvedCode()) - .isEqualTo(null); - } - - private DailyTest createDailyTest() { - LocalDateTime createDate = LocalDateTime.of(2023, 3, 5, 0, 0); - return DailyTest.create(createDate, "3월 5일 문제", createProblem()); - } - private List createProblem() { - return List.of( - Problem.builder() - .baekjoonProblemId(1L) - .problemTier(B5) - .problemStatus(ACTIVE) - .solvedCount(0L) - .build(), - Problem.builder() - .baekjoonProblemId(2L) - .problemTier(B5) - .problemStatus(ACTIVE) - .solvedCount(0L) - .build(), - Problem.builder() - .baekjoonProblemId(3L) - .problemTier(B5) - .problemStatus(ACTIVE) - .solvedCount(0L) - .build() - ); - } - private Member createMember() { - return Member.builder() - .email("user" + "@gmail.com") - .socialType(GOOGLE) - .nickname("nickname") - .description("description") - .build(); - } -} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepositoryTest.java similarity index 86% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepositoryTest.java index def61f60..821b64bf 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepositoryTest.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.member.MemberRepository; @@ -14,10 +14,10 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.*; -import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.CLOSE; -import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.*; +import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.CLOSE; +import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -36,7 +36,7 @@ class CustomDefenseRepositoryTest { private MemberRepository memberRepository; @Autowired - private CustomDefenseProblemsRepository customDefenseProblemsRepository; + private CustomDefenseProblemRepository customDefenseProblemsRepository; @AfterEach void tearDown() { customDefenseProblemsRepository.deleteAllInBatch(); diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java similarity index 90% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java index 6a2dd31a..1600b84f 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/customdefense/CustomDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java @@ -1,23 +1,18 @@ -package kr.co.morandi.backend.domain.contenttype.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense; import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.member.MemberRepository; import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.problem.ProblemRepository; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.*; diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java similarity index 67% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java index 95513d38..6be671a1 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestProblemsTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.dailytest; +package kr.co.morandi.backend.domain.defense.dailydefense; import kr.co.morandi.backend.domain.problem.Problem; import org.junit.jupiter.api.DisplayName; @@ -8,22 +8,22 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") -class DailyTestProblemsTest { +class DailyDefenseProblemTest { @DisplayName("오늘의 문제가 만들어졌을 때, 초기의 문제 제출횟수는 0이어야 한다.") @Test void submitCountIsZero() { // given List problems = createProblems(); LocalDateTime now = LocalDateTime.now(); - DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); - List dailyTestProblemsList = dailyTest.getDailyTestProblemsList(); + DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); + List DailyDefenseProblemsList = dailyDefense.getDailyDefenseProblems(); // when & then - assertThat(dailyTestProblemsList) + assertThat(DailyDefenseProblemsList) .extracting("submitCount") .containsExactlyInAnyOrder(0L, 0L, 0L); } @@ -34,11 +34,11 @@ void solvedCountIsZero() { // given List problems = createProblems(); LocalDateTime now = LocalDateTime.now(); - DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); - List dailyTestProblemsList = dailyTest.getDailyTestProblemsList(); + DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); + List DailyDefenseProblemsList = dailyDefense.getDailyDefenseProblems(); // when & then - assertThat(dailyTestProblemsList) + assertThat(DailyDefenseProblemsList) .extracting("solvedCount") .containsExactlyInAnyOrder(0L, 0L, 0L); } diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java similarity index 69% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java index f031cb1e..2c8f1983 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/dailytest/DailyTestTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.contenttype.dailytest; +package kr.co.morandi.backend.domain.defense.dailydefense; import kr.co.morandi.backend.domain.problem.Problem; import org.junit.jupiter.api.DisplayName; @@ -8,11 +8,11 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") -class DailyTestTest { +class DailyDefenseTest { @DisplayName("오늘의 문제 세트가 만들어진 시점에서 시도한 사람의 수는 0명 이어야 한다.") @Test void attemptCountIsZero() { @@ -21,10 +21,10 @@ void attemptCountIsZero() { LocalDateTime now = LocalDateTime.now(); // when - DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); // then - assertThat(dailyTest.getAttemptCount()).isZero(); + assertThat(dailyDefense.getAttemptCount()).isZero(); } @DisplayName("오늘의 문제가 만들어진 시점에 등록된 날짜는 만들어진 시점과 같아야 한다.") @Test @@ -34,10 +34,10 @@ void testDateEqualNow() { LocalDateTime now = LocalDateTime.now(); // when - DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); // then - assertThat(dailyTest.getDate()).isEqualTo(now); + assertThat(dailyDefense.getDate()).isEqualTo(now); } @DisplayName("오늘의 문제가 만들어진 이름은 일치해야한다.") @Test @@ -47,10 +47,10 @@ void contentNameIsEqual() { LocalDateTime now = LocalDateTime.now(); // when - DailyTest dailyTest = DailyTest.create(now, "오늘의 문제 테스트", problems); + DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); // then - assertThat(dailyTest.getContentName()).isEqualTo("오늘의 문제 테스트"); + assertThat(dailyDefense.getContentName()).isEqualTo("오늘의 문제 테스트"); } private List createProblems() { Problem problem1 = Problem.create(1L, B5, 0L); diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/DifficultyRangeTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java similarity index 83% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/DifficultyRangeTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java index 893d0c72..401981df 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/DifficultyRangeTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java @@ -1,13 +1,13 @@ -package kr.co.morandi.backend.domain.contenttype.randomcriteria; +package kr.co.morandi.backend.domain.defense.randomcriteria; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.tier.ProblemTier; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/RandomCriteriaTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java similarity index 91% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/RandomCriteriaTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java index adeb1e7e..9c0f00ac 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomcriteria/RandomCriteriaTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.domain.contenttype.randomcriteria; +package kr.co.morandi.backend.domain.defense.randomcriteria; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.contenttype.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.tier.ProblemTier; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseRepositoryTest.java similarity index 88% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseRepositoryTest.java index a510bf6a..2f252942 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/randomdefense/RandomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseRepositoryTest.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.contenttype.randomdefense; +package kr.co.morandi.backend.domain.defense.randomdefense; -import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; -import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefenseRepository; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.RandomDefense; +import kr.co.morandi.backend.domain.defense.random.RandomDefenseRepository; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,7 +13,7 @@ import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -35,7 +35,7 @@ void findByContentName() { randomDefenseRepository.saveAll(randomDefenses); // when - RandomDefense findRandomDefense = randomDefenseRepository.findById(randomDefenses.get(0).getContentTypeId()).get(); + RandomDefense findRandomDefense = randomDefenseRepository.findById(randomDefenses.get(0).getDefenseId()).get(); // then assertThat(findRandomDefense) diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java similarity index 87% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java index e878a8a7..b2baa583 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomdefense/RandomDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java @@ -1,12 +1,13 @@ -package kr.co.morandi.backend.domain.contenttype.random.randomdefense; +package kr.co.morandi.backend.domain.defense.randomdefense; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.RandomDefense; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java similarity index 75% rename from src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseTest.java rename to src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java index 78865818..eefc20d3 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contenttype/random/randomstagedefense/RandomStageDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java @@ -1,17 +1,18 @@ -package kr.co.morandi.backend.domain.contenttype.random.randomstagedefense; +package kr.co.morandi.backend.domain.defense.stagedefense; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.stagedefense.StageDefense; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @ActiveProfiles("test") -class RandomStageDefenseTest { +class StageDefenseTest { @DisplayName("스테이지 모드를 처음 만들 때 정보가 올바르게 저장되어야 한다.") @Test void createRamdomStageDefense() { @@ -20,7 +21,7 @@ void createRamdomStageDefense() { RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); // when - RandomStageDefense randomStageDefense = RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + StageDefense randomStageDefense = StageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); // then assertThat(randomStageDefense) @@ -37,7 +38,7 @@ void averageStageIsZero() { RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); // when - RandomStageDefense randomStageDefense = RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + StageDefense randomStageDefense = StageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); // then assertThat(randomStageDefense.getAverageStage()).isZero(); @@ -50,7 +51,7 @@ void attemptCountIsZero() { RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); // when - RandomStageDefense randomStageDefense = RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + StageDefense randomStageDefense = StageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); // then assertThat(randomStageDefense.getAttemptCount()).isZero(); @@ -63,7 +64,7 @@ void timeLimitGreatherThanZero() { RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); // when & then - assertThatThrownBy(() -> RandomStageDefense.create(randomCriteria, 0L, "브론즈 스테이지 모드")) + assertThatThrownBy(() -> StageDefense.create(randomCriteria, 0L, "브론즈 스테이지 모드")) .isInstanceOf(IllegalArgumentException.class) .hasMessage("스테이지 모드 제한 시간은 0보다 커야 합니다."); } diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java similarity index 64% rename from src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java index bf8c9cfd..d21f1ee9 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/customdefense/CustomDefenseProblemRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java @@ -1,10 +1,10 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.customdefense; +package kr.co.morandi.backend.domain.detail.customdefense; -import kr.co.morandi.backend.domain.contentrecord.customdefense.CustomDefenseRecord; -import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefense; -import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefenseProblems; +import kr.co.morandi.backend.domain.defense.customdefense.CustomDefense; +import kr.co.morandi.backend.domain.defense.customdefense.CustomDefenseProblem; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.customdefense.CustomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -12,17 +12,17 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.S5; +import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.S5; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @ActiveProfiles("test") -class CustomDefenseProblemRecordTest { +class CustomDetailTest { @DisplayName("CustomDefenseProblemRecord를 생성할 수 있다.") @Test @@ -31,18 +31,18 @@ void create() { CustomDefense customDefense = createCustomDefense(); Member member = createMember("member"); Problem problem = customDefense.getCustomDefenseProblems().stream() - .map(CustomDefenseProblems::getProblem) + .map(CustomDefenseProblem::getProblem) .findFirst() .orElse(null); - CustomDefenseRecord customDefenseRecord = mock(CustomDefenseRecord.class); + CustomRecord customDefenseRecord = mock(CustomRecord.class); // when - CustomDefenseProblemRecord customDefenseProblemRecord = CustomDefenseProblemRecord.create(member, problem, customDefenseRecord, customDefense); + CustomDetail customDefenseProblemRecord = CustomDetail.create(member, problem, customDefenseRecord, customDefense); // then assertThat(customDefenseProblemRecord).isNotNull() - .extracting("member", "problem", "contentType", "contentRecord") + .extracting("member", "problem", "defense", "record") .containsExactly( member, problem, customDefense, customDefenseRecord ); @@ -54,14 +54,14 @@ void initialSolvedTimeIsOne() { CustomDefense customDefense = createCustomDefense(); Member member = createMember("member"); Problem problem = customDefense.getCustomDefenseProblems().stream() - .map(CustomDefenseProblems::getProblem) + .map(CustomDefenseProblem::getProblem) .findFirst() .orElse(null); - CustomDefenseRecord customDefenseRecord = mock(CustomDefenseRecord.class); + CustomRecord customDefenseRecord = mock(CustomRecord.class); // when - CustomDefenseProblemRecord customDefenseProblemRecord = CustomDefenseProblemRecord.create(member, problem, customDefenseRecord, customDefense); + CustomDetail customDefenseProblemRecord = CustomDetail.create(member, problem, customDefenseRecord, customDefense); // then assertThat(customDefenseProblemRecord.getSolvedTime()) diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java new file mode 100644 index 00000000..2e9f2763 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java @@ -0,0 +1,133 @@ +package kr.co.morandi.backend.domain.detail.dailydefense; + +import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.dailydefense.DailyRecord; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@ActiveProfiles("test") +class DailyDetailTest { + @DisplayName("DailyDefenseProblemRecord를 만들 수 있다.") + @Test + void create() { + // given + DailyDefense DailyDefense = createDailyDefense(); + DailyRecord DailyDefenseRecord = mock(DailyRecord.class); + Problem problem = DailyDefense.getDailyDefenseProblems().stream() + .map(DailyDefenseProblem::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + + // then + assertThat(DailyDefenseProblemRecord).isNotNull() + .extracting("member", "problem", "defense", "record") + .contains(member, problem, DailyDefense, DailyDefenseRecord); + } + @DisplayName("DailyDefenseProblemRecord가 생성되면 isSolved는 false이다") + @Test + void initialIsSolvedFalse() { + // given + DailyDefense DailyDefense = createDailyDefense(); + DailyRecord DailyDefenseRecord = mock(DailyRecord.class); + Problem problem = DailyDefense.getDailyDefenseProblems().stream() + .map(DailyDefenseProblem::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + + // then + assertThat(DailyDefenseProblemRecord.getIsSolved()).isFalse(); + } + @DisplayName("DailyDefenseProblemRecord가 생성되면 submitCount는 0이다") + @Test + void initialSubmitCountIsZero() { + // given + DailyDefense DailyDefense = createDailyDefense(); + DailyRecord DailyDefenseRecord = mock(DailyRecord.class); + Problem problem = DailyDefense.getDailyDefenseProblems().stream() + .map(DailyDefenseProblem::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + + // then + assertThat(DailyDefenseProblemRecord.getSubmitCount()).isZero(); + } + @DisplayName("DailyDefenseProblemRecord가 생성되면 solvedCode는 null이다") + @Test + void initialSolvedCodeIsSetToNull() { + // given + DailyDefense DailyDefense = createDailyDefense(); + DailyRecord DailyDefenseRecord = mock(DailyRecord.class); + Problem problem = DailyDefense.getDailyDefenseProblems().stream() + .map(DailyDefenseProblem::getProblem) + .findFirst() + .orElse(null); + Member member = createMember(); + + // when + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + + // then + assertThat(DailyDefenseProblemRecord.getSolvedCode()) + .isEqualTo(null); + } + + private DailyDefense createDailyDefense() { + LocalDateTime createDate = LocalDateTime.of(2023, 3, 5, 0, 0); + return DailyDefense.create(createDate, "3월 5일 문제", createProblem()); + } + private List createProblem() { + return List.of( + Problem.builder() + .baekjoonProblemId(1L) + .problemTier(B5) + .problemStatus(ACTIVE) + .solvedCount(0L) + .build(), + Problem.builder() + .baekjoonProblemId(2L) + .problemTier(B5) + .problemStatus(ACTIVE) + .solvedCount(0L) + .build(), + Problem.builder() + .baekjoonProblemId(3L) + .problemTier(B5) + .problemStatus(ACTIVE) + .solvedCount(0L) + .build() + ); + } + private Member createMember() { + return Member.builder() + .email("user" + "@gmail.com") + .socialType(GOOGLE) + .nickname("nickname") + .description("description") + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java similarity index 63% rename from src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java index e8e6e157..c51385a0 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/RandomDefenseProblemRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java @@ -1,35 +1,35 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.random; +package kr.co.morandi.backend.domain.detail.randomdefense; -import kr.co.morandi.backend.domain.contentrecord.random.RandomDefenseRecord; -import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; +import kr.co.morandi.backend.domain.defense.random.RandomDefense; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.randomdefense.RandomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @ActiveProfiles("test") -class RandomDefenseProblemRecordTest { +class RandomDetailTest { @DisplayName("RandomDefenseProblemRecord를 생성한다.") @Test void create() { // given RandomDefense randomDefense = mock(RandomDefense.class); - RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + RandomRecord randomDefenseRecord = mock(RandomRecord.class); Problem problem = createProblem(); Member member = createMember("test"); // when - RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord).isNotNull() - .extracting("member", "problem", "contentType", "contentRecord") + .extracting("member", "problem", "defense", "record") .contains(member, problem, randomDefense, randomDefenseRecord); } @@ -38,12 +38,12 @@ void create() { void initialSolvedTimeIsZero() { // given RandomDefense randomDefense = mock(RandomDefense.class); - RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + RandomRecord randomDefenseRecord = mock(RandomRecord.class); Problem problem = createProblem(); Member member = createMember("test"); // when - RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getSolvedTime()).isZero(); @@ -53,12 +53,12 @@ void initialSolvedTimeIsZero() { void initialIsSolvedFalse() { // given RandomDefense randomDefense = mock(RandomDefense.class); - RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + RandomRecord randomDefenseRecord = mock(RandomRecord.class); Problem problem = createProblem(); Member member = createMember("test"); // when - RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getIsSolved()).isFalse(); @@ -68,13 +68,12 @@ void initialIsSolvedFalse() { void initialSubmitCountIsZero() { // given RandomDefense randomDefense = mock(RandomDefense.class); - RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + RandomRecord randomDefenseRecord = mock(RandomRecord.class); Problem problem = createProblem(); Member member = createMember("test"); // when - RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); - + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getSubmitCount()).isZero(); } @@ -83,12 +82,12 @@ void initialSubmitCountIsZero() { void initialSolvedCodeIsSetToNull() { // given RandomDefense randomDefense = mock(RandomDefense.class); - RandomDefenseRecord randomDefenseRecord = mock(RandomDefenseRecord.class); + RandomRecord randomDefenseRecord = mock(RandomRecord.class); Problem problem = createProblem(); Member member = createMember("test"); // when - RandomDefenseProblemRecord randomDefenseProblemRecord = RandomDefenseProblemRecord.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getSolvedCode()) diff --git a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java similarity index 59% rename from src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java index f4fbb5dc..85a941e5 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contentproblemrecord/random/StageDefenseProblemRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java @@ -1,32 +1,33 @@ -package kr.co.morandi.backend.domain.contentproblemrecord.random; +package kr.co.morandi.backend.domain.detail.randomdefense; -import kr.co.morandi.backend.domain.contentrecord.random.StageDefenseRecord; -import kr.co.morandi.backend.domain.contenttype.random.randomstagedefense.RandomStageDefense; +import kr.co.morandi.backend.domain.defense.stagedefense.StageDefense; +import kr.co.morandi.backend.domain.detail.stagedefense.StageDetail; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.stagedefense.StageRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @ActiveProfiles("test") -class StageDefenseProblemRecordTest { +class StageDetailTest { @DisplayName("스테이지 문제 기록이 생성되면 초기 정답 시간은 0이다.") @Test void initialSolvedTimeIsZero() { // given - RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); - StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + StageDefense randomStageDefense = mock(StageDefense.class); + StageRecord stageDefenseRecord = mock(StageRecord.class); Problem problem = createProblem(); Member member = createMember(); // when - StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord.getSolvedTime()).isEqualTo(0L); @@ -36,14 +37,13 @@ void initialSolvedTimeIsZero() { @Test void createStageDefenseProblemRecordWithStageNumber() { // given - RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); - StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + StageDefense randomStageDefense = mock(StageDefense.class); + StageRecord stageDefenseRecord = mock(StageRecord.class); Problem problem = createProblem(); Member member = createMember(); // when - StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); - + StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord.getStageNumber()).isEqualTo(1L); @@ -52,13 +52,13 @@ void createStageDefenseProblemRecordWithStageNumber() { @Test void initialIsSolvedIsFalse() { // given - RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); - StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + StageDefense randomStageDefense = mock(StageDefense.class); + StageRecord stageDefenseRecord = mock(StageRecord.class); Problem problem = createProblem(); Member member = createMember(); // when - StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) @@ -70,14 +70,13 @@ void initialIsSolvedIsFalse() { @Test void initialSubmitCountIsZero() { // given - RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); - StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + StageDefense randomStageDefense = mock(StageDefense.class); + StageRecord stageDefenseRecord = mock(StageRecord.class); Problem problem = createProblem(); Member member = createMember(); // when - StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); - + StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) .extracting("submitCount") @@ -88,13 +87,13 @@ void initialSubmitCountIsZero() { @Test void initialSolvedCodeIsNull() { // given - RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); - StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + StageDefense randomStageDefense = mock(StageDefense.class); + StageRecord stageDefenseRecord = mock(StageRecord.class); Problem problem = createProblem(); Member member = createMember(); // when - StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) @@ -106,17 +105,16 @@ void initialSolvedCodeIsNull() { @Test void createStageDefenseProblemRecord() { // given - RandomStageDefense randomStageDefense = mock(RandomStageDefense.class); - StageDefenseRecord stageDefenseRecord = mock(StageDefenseRecord.class); + StageDefense randomStageDefense = mock(StageDefense.class); + StageRecord stageDefenseRecord = mock(StageRecord.class); Problem problem = createProblem(); Member member = createMember(); // when - StageDefenseProblemRecord stageDefenseProblemRecord = StageDefenseProblemRecord.create(1L, member, problem, stageDefenseRecord, randomStageDefense); - + StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) - .extracting("member", "problem", "contentRecord", "contentType") + .extracting("member", "problem", "record", "defense") .contains(member, problem, stageDefenseRecord, randomStageDefense); } private Member createMember() { diff --git a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java index b3692ccf..6a0b07de 100644 --- a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java @@ -9,7 +9,7 @@ import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; diff --git a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java index 22738f36..f3129230 100644 --- a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java @@ -5,7 +5,7 @@ import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static kr.co.morandi.backend.domain.problem.ProblemStatus.INIT; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecordTest.java similarity index 76% rename from src/test/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecordTest.java index e5a15860..d147adb6 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contentrecord/customdefense/CustomDefenseRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecordTest.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.domain.contentrecord.customdefense; +package kr.co.morandi.backend.domain.record.customdefense; -import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefense; -import kr.co.morandi.backend.domain.contenttype.customdefense.CustomDefenseProblems; +import kr.co.morandi.backend.domain.defense.customdefense.CustomDefense; +import kr.co.morandi.backend.domain.defense.customdefense.CustomDefenseProblem; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; import org.junit.jupiter.api.DisplayName; @@ -11,15 +11,15 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.customdefense.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.contenttype.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.S5; +import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.S5; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") -class CustomDefenseRecordTest { +class CustomRecordTest { @DisplayName("커스텀 디펜스 기록이 만들어 졌을 때 시험 날짜는 시작한 시점과 같아야 한다.") @Test void testDateIsEqualNow() { @@ -31,7 +31,7 @@ void testDateIsEqualNow() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // then assertThat(customDefenseRecord.getTestDate()).isEqualTo(startTime); @@ -47,7 +47,7 @@ void solvedCountIsZero() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // then assertThat(customDefenseRecord.getSolvedCount()).isZero(); @@ -63,7 +63,7 @@ void problemCountIsEqual() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // then assertThat(customDefenseRecord.getProblemCount()).isEqualTo(customDefense.getProblemCount()); @@ -79,7 +79,7 @@ void totalSolvedTimeIsZero() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // then assertThat(customDefenseRecord.getTotalSolvedTime()).isZero(); @@ -95,10 +95,10 @@ void isSolvedFalse() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // then - assertThat(customDefenseRecord.getContentProblemRecords()) + assertThat(customDefenseRecord.getDetails()) .extracting("isSolved") .containsExactlyInAnyOrder( false, @@ -116,10 +116,10 @@ void solvedTimeIsZero() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // then - assertThat(customDefenseRecord.getContentProblemRecords()) + assertThat(customDefenseRecord.getDetails()) .extracting("solvedTime") .containsExactlyInAnyOrder( 0L, @@ -137,10 +137,10 @@ void submitCountIsZero() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // when & then - assertThat(customDefenseRecord.getContentProblemRecords()) + assertThat(customDefenseRecord.getDetails()) .extracting("submitCount") .containsExactlyInAnyOrder( 0L, @@ -158,10 +158,10 @@ void solvedCodeIsNull() { LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); // when - CustomDefenseRecord customDefenseRecord = CustomDefenseRecord.create(customDefense, member, startTime, problems); + CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // when & then - assertThat(customDefenseRecord.getContentProblemRecords()) + assertThat(customDefenseRecord.getDetails()) .extracting("solvedCode") .containsExactlyInAnyOrder( null, @@ -182,10 +182,10 @@ private CustomDefense createCustomDefense() { "custom_defense", OPEN, GOLD, 60L, now); } private List getCustomDefenseProblems(CustomDefense customDefense) { - List customDefenseProblems = customDefense.getCustomDefenseProblems(); + List customDefenseProblems = customDefense.getCustomDefenseProblems(); return customDefenseProblems.stream() - .map(CustomDefenseProblems::getProblem) + .map(CustomDefenseProblem::getProblem) .toList(); } private Member createMember(String name) { diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecordTest.java similarity index 59% rename from src/test/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecordTest.java index 7ae1cae1..7cc1d869 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contentrecord/dailytest/DailyTestRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecordTest.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.contentrecord.dailytest; +package kr.co.morandi.backend.domain.record.dailydefense; -import kr.co.morandi.backend.domain.contentproblemrecord.ContentProblemRecord; -import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTest; -import kr.co.morandi.backend.domain.contenttype.dailytest.DailyTestProblems; +import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; import org.junit.jupiter.api.DisplayName; @@ -12,7 +12,7 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -20,42 +20,43 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; @ActiveProfiles("test") -class DailyTestRecordTest { +class DailyRecordTest { @DisplayName("오늘의 문제 기록이 만들어졌을 때 푼 문제 수는 0문제 이어야 한다.") @Test void solvedCountIsZero() { // given - DailyTest dailyTest = createDailyTest(); + DailyDefense DailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - List problems = getProblemList(dailyTest); + List problems = getProblemList(DailyDefense); // when - DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); // then - assertThat(dailyTestRecord.getSolvedCount()).isZero(); + assertThat(DailyDefenseRecord.getSolvedCount()).isZero(); } @DisplayName("오늘의 문제 기록이 만들어졌을 때 전체 문제 수는 오늘의 문제에 출제된 문제 들과 같아야 한다.") @Test void problemCountIsEqual() { // given - DailyTest dailyTest = createDailyTest(); + DailyDefense DailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - List problems = getProblemList(dailyTest); + List problems = getProblemList(DailyDefense); // when - DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); + // then - assertThat(dailyTestRecord.getProblemCount()).isEqualTo(dailyTest.getProblemCount()); - assertThat(dailyTestRecord.getContentProblemRecords()) - .extracting("problem", "contentRecord") + assertThat(DailyDefenseRecord.getProblemCount()).isEqualTo(DailyDefense.getProblemCount()); + assertThat(DailyDefenseRecord.getDetails()) + .extracting("problem", "record") .containsExactlyInAnyOrder( - tuple(problems.get(0), dailyTestRecord), - tuple(problems.get(1), dailyTestRecord), - tuple(problems.get(2), dailyTestRecord) + tuple(problems.get(0), DailyDefenseRecord), + tuple(problems.get(1), DailyDefenseRecord), + tuple(problems.get(2), DailyDefenseRecord) ); } @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가면 예외가 발생한다.") @@ -63,15 +64,15 @@ void problemCountIsEqual() { void recordCreateExceptionWhenOverOneDay() { // given LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); - DailyTest dailyTest = createDailyTest(createdTime); + DailyDefense DailyDefense = createDailyDefense(createdTime); Member member = createMember("user"); - List problems = getProblemList(dailyTest); + List problems = getProblemList(DailyDefense); LocalDateTime startTime = LocalDateTime.of(2024, 3, 2, 0, 0, 0); // when & then - assertThatThrownBy(() -> DailyTestRecord.create(startTime, dailyTest, member, problems)) + assertThatThrownBy(() -> DailyRecord.create(startTime, DailyDefense, member, problems)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("오늘의 문제 기록은 출제 시점으로부터 하루 이내에 생성되어야 합니다."); } @@ -80,31 +81,31 @@ void recordCreateExceptionWhenOverOneDay() { void recordCreatedWithinOneDay() { // given LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); - DailyTest dailyTest = createDailyTest(createdTime); + DailyDefense DailyDefense = createDailyDefense(createdTime); Member member = createMember("user"); - List problems = getProblemList(dailyTest); + List problems = getProblemList(DailyDefense); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 23, 59, 59); // when - DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); + DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); // then - assertNotNull(dailyTestRecord); + assertNotNull(DailyDefenseRecord); } @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 여부는 모두 오답 상태여야 한다.") @Test void isSolvedIsFalse() { // given - DailyTest dailyTest = createDailyTest(); + DailyDefense DailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - List problems = getProblemList(dailyTest); + List problems = getProblemList(DailyDefense); // when - DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); - List contentProblemRecords = dailyTestRecord.getContentProblemRecords(); + DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); + List contentProblemRecords = DailyDefenseRecord.getDetails(); // then assertThat(contentProblemRecords) @@ -115,14 +116,14 @@ void isSolvedIsFalse() { @Test void submitCountIsZero() { // given - DailyTest dailyTest = createDailyTest(); + DailyDefense DailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - List problems = getProblemList(dailyTest); + List problems = getProblemList(DailyDefense); // when - DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime, dailyTest, member, problems); - List contentProblemRecords = dailyTestRecord.getContentProblemRecords(); + DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); + List contentProblemRecords = DailyDefenseRecord.getDetails(); // then assertThat(contentProblemRecords) @@ -133,33 +134,32 @@ void submitCountIsZero() { @Test void solvedCodeIsNull() { // given - DailyTest dailyTest = createDailyTest(); + DailyDefense DailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - List problems = getProblemList(dailyTest); + List problems = getProblemList(DailyDefense); // when - DailyTestRecord dailyTestRecord = DailyTestRecord.create(startTime , dailyTest, member, problems); - List contentProblemRecords = dailyTestRecord.getContentProblemRecords(); - + DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); + List contentProblemRecords = DailyDefenseRecord.getDetails(); // then assertThat(contentProblemRecords) .extracting("solvedCode") .containsExactlyInAnyOrder(null, null, null); } - private List getProblemList(DailyTest dailyTest) { - return dailyTest.getDailyTestProblemsList().stream() - .map(DailyTestProblems::getProblem) + private List getProblemList(DailyDefense DailyDefense) { + return DailyDefense.getDailyDefenseProblems().stream() + .map(DailyDefenseProblem::getProblem) .toList(); } - private DailyTest createDailyTest() { + private DailyDefense createDailyDefense() { List problems = createProblems(); LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); - return DailyTest.create(createdTime, "오늘의 문제 테스트", problems); + return DailyDefense.create(createdTime, "오늘의 문제 테스트", problems); } - private DailyTest createDailyTest(LocalDateTime createdTime) { + private DailyDefense createDailyDefense(LocalDateTime createdTime) { List problems = createProblems(); - return DailyTest.create(createdTime, "오늘의 문제 테스트", problems); + return DailyDefense.create(createdTime, "오늘의 문제 테스트", problems); } private List createProblems() { Problem problem1 = Problem.create(1L, B5, 0L); diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java similarity index 79% rename from src/test/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java index f8e088ea..84e485e1 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/RandomDefenseRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.domain.contentrecord.random; +package kr.co.morandi.backend.domain.record.randomdefense; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.contenttype.random.randomdefense.RandomDefense; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.RandomDefense; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; import org.junit.jupiter.api.DisplayName; @@ -11,12 +11,12 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") -class RandomDefenseRecordTest { +class RandomRecordTest { @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 맞춘 문제수는 0문제 이어야 한다.") @Test void solvedCountIsZero() { @@ -28,7 +28,7 @@ void solvedCountIsZero() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then assertThat(randomDefenseRecord.getSolvedCount()).isZero(); @@ -44,7 +44,7 @@ void problemCountIsEqual() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then assertThat(randomDefenseRecord.getProblemCount()).isEqualTo(randomDefense.getProblemCount()); @@ -61,7 +61,7 @@ void totalSolvedTimeIsZero() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then assertThat(randomDefenseRecord.getTotalSolvedTime()).isZero(); @@ -77,7 +77,7 @@ void testDateIsEqualTestDate() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then assertThat(randomDefenseRecord.getTestDate()).isEqualTo(now); @@ -93,10 +93,10 @@ void isSolvedIsFalse() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then - assertThat(randomDefenseRecord.getContentProblemRecords()) + assertThat(randomDefenseRecord.getDetails()) .extracting("isSolved") .containsExactly( false, @@ -116,10 +116,10 @@ void submitCountIsZero() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then - assertThat(randomDefenseRecord.getContentProblemRecords()) + assertThat(randomDefenseRecord.getDetails()) .extracting("submitCount") .containsExactly( 0L, @@ -139,11 +139,10 @@ void solvedCodeIsNull() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord - = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then - assertThat(randomDefenseRecord.getContentProblemRecords()) + assertThat(randomDefenseRecord.getDetails()) .extracting("solvedCode") .containsExactly( null, @@ -162,11 +161,10 @@ void solvedTimeIsZero() { LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); // when - RandomDefenseRecord randomDefenseRecord - = RandomDefenseRecord.create(randomDefense, member, now, problems); + RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then - assertThat(randomDefenseRecord.getContentProblemRecords()) + assertThat(randomDefenseRecord.getDetails()) .extracting("solvedTime") .containsExactly( 0L, diff --git a/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/StageRecordTest.java similarity index 64% rename from src/test/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/record/randomdefense/StageRecordTest.java index ec50bbcc..72c43f90 100644 --- a/src/test/java/kr/co/morandi/backend/domain/contentrecord/random/StageDefenseRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/StageRecordTest.java @@ -1,34 +1,35 @@ -package kr.co.morandi.backend.domain.contentrecord.random; +package kr.co.morandi.backend.domain.record.randomdefense; -import kr.co.morandi.backend.domain.contenttype.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.contenttype.random.randomstagedefense.RandomStageDefense; +import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.stagedefense.StageDefense; import kr.co.morandi.backend.domain.member.Member; import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.record.stagedefense.StageRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.contenttype.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") -class StageDefenseRecordTest { +class StageRecordTest { @DisplayName("스테이지 기록이 만들어졌을 때 포함된 문제 수는 1개다.") @Test void stageCountIsOne() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, startTime, member, problem); // then assertThat(stageDefenseRecord.getStageCount()).isOne(); @@ -37,17 +38,17 @@ void stageCountIsOne() { @Test void initialStageNumberIsSetToOne() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, startTime, member, problem); // then - assertThat(stageDefenseRecord.getContentProblemRecords()) + assertThat(stageDefenseRecord.getDetails()) .extracting("stageNumber") .containsExactly(1L); } @@ -55,14 +56,14 @@ void initialStageNumberIsSetToOne() { @Test void totalSolvedTimeIsZero() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, startTime, member, problem); // then assertThat(stageDefenseRecord.getTotalSolvedTime()).isZero(); @@ -71,14 +72,14 @@ void totalSolvedTimeIsZero() { @Test void testDateEqualNow() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, startTime, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, startTime, member, problem); // then assertThat(stageDefenseRecord.getTestDate()).isEqualTo(startTime); @@ -87,17 +88,17 @@ void testDateEqualNow() { @Test void solvedTimeIsZero() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, now, member, problem); // then - assertThat(stageDefenseRecord.getContentProblemRecords()) + assertThat(stageDefenseRecord.getDetails()) .extracting("solvedTime") .containsExactly(0L); } @@ -105,17 +106,17 @@ void solvedTimeIsZero() { @Test void isSolvedIsFalse() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, now, member, problem); // then - assertThat(stageDefenseRecord.getContentProblemRecords()) + assertThat(stageDefenseRecord.getDetails()) .extracting("isSolved") .containsExactly(false); } @@ -123,17 +124,17 @@ void isSolvedIsFalse() { @Test void submitCountIsZero() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, now, member, problem); // then - assertThat(stageDefenseRecord.getContentProblemRecords()) + assertThat(stageDefenseRecord.getDetails()) .extracting("submitCount") .containsExactly(0L); } @@ -141,24 +142,24 @@ void submitCountIsZero() { @Test void solvedCodeIsNull() { // given - RandomStageDefense randomStageDefense = createRandomStageDefense(); + StageDefense randomStageDefense = createRandomStageDefense(); Problem problem = Problem.create(1L, B5, 100L); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0, 0, 0); Member member = createMember("user"); // when - StageDefenseRecord stageDefenseRecord = StageDefenseRecord.create(randomStageDefense, now, member, problem); + StageRecord stageDefenseRecord = StageRecord.create(randomStageDefense, now, member, problem); // then - assertThat(stageDefenseRecord.getContentProblemRecords()) + assertThat(stageDefenseRecord.getDetails()) .extracting("solvedCode") .containsExactly((String)null); } - private RandomStageDefense createRandomStageDefense() { + private StageDefense createRandomStageDefense() { RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); - return RandomStageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + return StageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); } private Member createMember(String name) { return Member.create(name, name + "@gmail.com", GOOGLE, name, name); From a17a6d1dbc8889473d0dcd1c4d58c18937d35f4b Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:23:12 +0900 Subject: [PATCH 08/14] =?UTF-8?q?DailyDefense=20=EC=8B=9C=EC=9E=91=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20=EB=B0=8F=20=EC=B6=9C=EC=A0=9C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=A0=95=EC=9D=98=20(#31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 패키지 구조 변경 및 DailyTest 시험 일은 LocalDate로 변경 * feat: 출제로직 ProblemGenerationService 생성 * feat: DailyDefenseAdapter 생성 | 패키지 구조 변경 * chore: Test 패키지 구조 변경 * test: ProblemGenerationServiceTest getDefenseProblems 실패 테스트 * feat: DailyDefenseProblem 출제 로직 기능 구현 * feat: DailyDefense start usecase 생성 * chore: 함수 파라미터 inline으로 변경 * chore: CustomRecord 생성자 파라미터 inline으로 변경 * fix: customdefenseproblem과 dailydefenseproblem의 problemNumber를 추가함으로 발생하는 로직 변경 및 테스트 코드 일괄 변경 defense record를 만들 때 파라미터를 map으로 변경하여 문제번호를 함께 관리 * feat: DailyDefense 시험 응시 구현 중복 문제 처리는 DailyRecord 책임으로 * fix: stream에서 toList를 반환하면 Immutable -> ArrayList 반환하도록 변경 * :white_check_mark: 오늘의 문제 중복 삽입 테스트 * :art: 변수명 camel case 변경 * :art: 변수명 카멜 케이스 변경 * :art: DailyDefenseService 변수명 변경 및 구조 변경 * :art: Record에 Detail 제네릭 사용하여 getDetail 시 캐스팅 필요 없게 만듦 * fix: Record raw type에서 Record로 변경 * refactor: 전체 패키지 헥사고날 아키텍처 적용 * test: 테스트 실패하여 DailyDefenseProblemRepositoryTest tearDown 메소드 추가 * feat: SessionDetail과 TempCode기능 추가 및 테스트 작성 * feat: Defense에 응시할 시 끝나는 시간을 계산하는 메소드 추가 * feat: DefenseSession 추가 및 테스트코드 작성 * feat: DailyDefense 시작 service 및 테스트코드 작성 * feat: DailyDefenseProblem 랜덤 출제 기능 및 테스트코드 작성 * test: teardown 메소드 추가 * test: DailyDefense create시 problem list-> map 변경 및 테스트코드 수정 * feat: 조건에 따라 DailyDefense를 생성하는 기능 구현 및 테스트코드 작성 * fix: DailyDefenseProblemAdapterTest 수정 * feat: Scheduling 적용 * fix: DefenseType @Enumerated Option 추가 * :pencil2: Sonarcloud issue 해결 * :art: DTO 변환 로직 DTO내 이동 * :fire: 패키지 통일 defensemanagement --- .../dailydefense/DailyDefensePort.java | 12 + .../dailydefense/DailyDefenseProblemPort.java | 11 + .../defensesession/DefenseSessionPort.java | 13 + .../sessiondetail/SessionDetailPort.java | 10 + .../record/dailyrecord/DailyRecordPort.java | 15 ++ .../algorithm/{ => model}/Algorithm.java | 2 +- .../domain/bookmark/{ => model}/BookMark.java | 4 +- .../{ => model}/ContentMemberLikes.java | 4 +- .../backend/domain/defense/Defense.java | 25 +- .../backend/domain/defense/DefenseType.java | 6 + .../{ => model}/CustomDefense.java | 41 +-- .../{ => model}/CustomDefenseProblem.java | 18 +- .../{ => model}/DefenseTier.java | 2 +- .../customdefense/{ => model}/Visibility.java | 2 +- .../service/CustomDefenseStrategy.java | 24 ++ .../defense/dailydefense/DailyDefense.java | 44 ---- .../DailyDefenseProblemRepository.java | 6 - .../dailydefense/DailyDefenseRepository.java | 7 - .../dailydefense/model/DailyDefense.java | 70 +++++ .../{ => model}/DailyDefenseProblem.java | 17 +- .../DailyDefenseGenerationService.java | 62 +++++ .../service/DailyDefenseStrategy.java | 34 +++ .../ProblemGenerationStrategy.java | 14 + .../service/ProblemGenerationService.java | 26 ++ .../random/{ => model}/RandomDefense.java | 16 +- .../randomcriteria/RandomCriteria.java | 4 +- .../stagedefense/StageDefenseRepository.java | 6 - .../{ => model}/StageDefense.java | 27 +- .../domain/defense/tier/ProblemTier.java | 18 -- .../defense/tier/model/ProblemTier.java | 33 +++ .../StartDailyDefenseServiceRequest.java | 20 ++ .../response/DefenseProblemResponse.java | 63 +++++ .../StartDailyDefenseServiceResponse.java | 52 ++++ .../DailyDefenseManagementService.java | 74 ++++++ .../session/model/DefenseSession.java | 101 ++++++++ .../session/model/ExamStatus.java | 5 + .../sessiondetail/model/SessionDetail.java | 85 ++++++ .../tempcode/model/Language.java | 30 +++ .../tempcode/model/TempCode.java | 60 +++++ .../morandi/backend/domain/detail/Detail.java | 11 +- .../customdefense/CustomDetailRepository.java | 6 - .../{ => model}/CustomDetail.java | 17 +- .../detail/dailydefense/DailyDetail.java | 27 -- .../dailydefense/model/DailyDetail.java | 31 +++ .../{ => model}/RandomDetail.java | 18 +- .../stagedefense/{ => model}/StageDetail.java | 16 +- .../detail/submit/{ => model}/SubmitCode.java | 4 +- .../domain/member/{ => model}/Member.java | 2 +- .../domain/member/{ => model}/SocialType.java | 2 +- .../domain/problem/ProblemRepository.java | 11 - .../domain/problem/{ => model}/Problem.java | 6 +- .../problem/{ => model}/ProblemStatus.java | 2 +- .../{ => model}/ProblemAlgorithm.java | 6 +- .../morandi/backend/domain/record/Record.java | 29 ++- .../CustomDefenseRecordRepository.java | 6 - .../model}/CustomRecord.java | 27 +- .../record/dailydefense/DailyRecord.java | 48 ---- .../record/dailyrecord/model/DailyRecord.java | 77 ++++++ .../model}/RandomRecord.java | 27 +- .../model}/StageRecord.java | 27 +- .../dailydefense/DailyDefenseAdapter.java | 29 +++ .../DailyDefenseProblemAdapter.java | 46 ++++ .../dailyrecord/DailyRecordAdapter.java | 32 +++ .../defensesession/DefenseSessionAdapter.java | 30 +++ .../config/AlgorithmInitializer.java | 6 +- .../config/JpaAuditingConfig.java | 2 +- .../config/SchedulingConfig.java | 9 + .../algorithm/AlgorithmRepository.java | 3 +- .../bookmark/BookMarkRepository.java | 3 +- .../defense/DefenseRepository.java | 7 + .../CustomDefenseProblemRepository.java | 3 +- .../CustomDefenseRepository.java | 4 +- .../DailyDefenseProblemRepository.java | 17 ++ .../dailydefense/DailyDefenseRepository.java | 22 ++ .../random/RandomDefenseRepository.java | 3 +- .../stagedefense/StageDefenseRepository.java | 7 + .../session/DefenseSessionRepository.java | 22 ++ .../SessionDetailRepository.java | 7 + .../tempcode/TempCodeRepository.java | 7 + .../ContentMemberLikesRepository.java | 3 +- .../persistence}/member/MemberRepository.java | 3 +- .../problem/ProblemRepository.java | 29 +++ .../ProblemAlgorithmRepository.java | 3 +- .../CustomDefenseRecordRepository.java | 7 + .../dailyrecord/DailyRecordRepository.java | 34 +++ .../DailyDefenseGenerationScheduler.java | 29 +++ .../dto/request/StartDailyDefenseRequest.java | 25 ++ .../DefenseSessionPortTest.java | 115 +++++++++ .../customdefense/CustomDefenseTest.java | 38 ++- .../dailydefense/DailyDefenseProblemTest.java | 41 ++- .../dailydefense/DailyDefenseTest.java | 88 ++++++- .../DailyDefenseGenerationServiceTest.java | 86 +++++++ .../randomcriteria/DifficultyRangeTest.java | 8 +- .../randomcriteria/RandomCriteriaTest.java | 4 +- .../randomdefense/RandomDefenseTest.java | 27 +- .../service/ProblemGenerationServiceTest.java | 86 +++++++ .../stagedefense/StageDefenseTest.java | 28 +- .../DailyDefenseManagementServiceTest.java | 241 ++++++++++++++++++ .../session/model/DefenseSessionTest.java | 205 +++++++++++++++ .../model/SessionDetailTest.java | 132 ++++++++++ .../customdefense/CustomDetailTest.java | 25 +- .../detail/dailydefense/DailyDetailTest.java | 37 +-- .../randomdefense/RandomDetailTest.java | 23 +- .../detail/randomdefense/StageDetailTest.java | 28 +- .../backend/domain/member/MemberTest.java | 4 +- .../backend/domain/problem/ProblemTest.java | 6 +- .../CustomRecordTest.java | 46 ++-- .../record/dailydefense/DailyRecordTest.java | 173 ------------- .../record/dailyrecord/DailyRecordTest.java | 193 ++++++++++++++ .../randomdefense/RandomRecordTest.java | 41 +-- .../StageRecordTest.java | 18 +- .../DailyDefenseProblemAdapterTest.java | 126 +++++++++ .../dailyrecord/DailyRecordAdapterTest.java | 126 +++++++++ .../DefenseSessionAdapterTest.java | 76 ++++++ .../adapter/problem/ProblemAdapterTest.java | 9 + .../config/AlgorithmInitializerTest.java | 6 +- .../algorithm/AlgorithmRepositoryTest.java | 4 +- .../CustomDefenseRepositoryTest.java | 25 +- .../DailyDefenseProblemRepositoryTest.java | 77 ++++++ .../RandomDefenseRepositoryTest.java | 10 +- .../dailydetail/DailyDetailRepository.java | 7 + .../member/MemberRepositoryTest.java | 7 +- .../problem/ProblemRepositoryTest.java | 37 ++- .../DailyRecordRepositoryTest.java | 156 ++++++++++++ 124 files changed, 3524 insertions(+), 690 deletions(-) create mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java create mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java create mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPort.java create mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java create mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/record/dailyrecord/DailyRecordPort.java rename src/main/java/kr/co/morandi/backend/domain/algorithm/{ => model}/Algorithm.java (93%) rename src/main/java/kr/co/morandi/backend/domain/bookmark/{ => model}/BookMark.java (84%) rename src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/{ => model}/ContentMemberLikes.java (85%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java rename src/main/java/kr/co/morandi/backend/domain/defense/customdefense/{ => model}/CustomDefense.java (81%) rename src/main/java/kr/co/morandi/backend/domain/defense/customdefense/{ => model}/CustomDefenseProblem.java (60%) rename src/main/java/kr/co/morandi/backend/domain/defense/customdefense/{ => model}/DefenseTier.java (78%) rename src/main/java/kr/co/morandi/backend/domain/defense/customdefense/{ => model}/Visibility.java (74%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefense.java rename src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/{ => model}/DailyDefenseProblem.java (59%) create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationService.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseStrategy.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/service/ProblemGenerationService.java rename src/main/java/kr/co/morandi/backend/domain/defense/random/{ => model}/RandomDefense.java (77%) rename src/main/java/kr/co/morandi/backend/domain/defense/random/{ => model}/randomcriteria/RandomCriteria.java (94%) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java rename src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/{ => model}/StageDefense.java (71%) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/tier/ProblemTier.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/tier/model/ProblemTier.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/request/StartDailyDefenseServiceRequest.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/DefenseProblemResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/StartDailyDefenseServiceResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementService.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSession.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/ExamStatus.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetail.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/Language.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/TempCode.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java rename src/main/java/kr/co/morandi/backend/domain/detail/customdefense/{ => model}/CustomDetail.java (51%) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/model/DailyDetail.java rename src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/{ => model}/RandomDetail.java (51%) rename src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/{ => model}/StageDetail.java (52%) rename src/main/java/kr/co/morandi/backend/domain/detail/submit/{ => model}/SubmitCode.java (86%) rename src/main/java/kr/co/morandi/backend/domain/member/{ => model}/Member.java (96%) rename src/main/java/kr/co/morandi/backend/domain/member/{ => model}/SocialType.java (81%) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java rename src/main/java/kr/co/morandi/backend/domain/problem/{ => model}/Problem.java (86%) rename src/main/java/kr/co/morandi/backend/domain/problem/{ => model}/ProblemStatus.java (92%) rename src/main/java/kr/co/morandi/backend/domain/problemalgorithm/{ => model}/ProblemAlgorithm.java (77%) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomDefenseRecordRepository.java rename src/main/java/kr/co/morandi/backend/domain/record/{customdefense => customdefenserecord/model}/CustomRecord.java (54%) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java create mode 100644 src/main/java/kr/co/morandi/backend/domain/record/dailyrecord/model/DailyRecord.java rename src/main/java/kr/co/morandi/backend/domain/record/{randomdefense => randomrecord/model}/RandomRecord.java (59%) rename src/main/java/kr/co/morandi/backend/domain/record/{stagedefense => stagerecord/model}/StageRecord.java (55%) create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapter.java rename src/main/java/kr/co/morandi/backend/{ => infrastructure}/config/AlgorithmInitializer.java (88%) rename src/main/java/kr/co/morandi/backend/{ => infrastructure}/config/JpaAuditingConfig.java (79%) create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/config/SchedulingConfig.java rename src/main/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/algorithm/AlgorithmRepository.java (63%) rename src/main/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/bookmark/BookMarkRepository.java (52%) create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java rename src/main/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/defense/customdefense/CustomDefenseProblemRepository.java (50%) rename src/main/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/defense/customdefense/CustomDefenseRepository.java (50%) create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseRepository.java rename src/main/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/defense/random/RandomDefenseRepository.java (51%) create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/session/DefenseSessionRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java rename src/main/java/kr/co/morandi/backend/{domain/contentmemberlikes => infrastructure/persistence/defensememberlikes}/ContentMemberLikesRepository.java (50%) rename src/main/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/member/MemberRepository.java (60%) create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepository.java rename src/main/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/problemalgorithm/ProblemAlgorithmRepository.java (50%) create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java create mode 100644 src/main/java/kr/co/morandi/backend/web/defensemanagement/dailydefense/dto/request/StartDailyDefenseRequest.java create mode 100644 src/test/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPortTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/defense/service/ProblemGenerationServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSessionTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetailTest.java rename src/test/java/kr/co/morandi/backend/domain/record/{customdefense => customdefenserecord}/CustomRecordTest.java (80%) delete mode 100644 src/test/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/domain/record/dailyrecord/DailyRecordTest.java rename src/test/java/kr/co/morandi/backend/domain/record/{randomdefense => stagerecord}/StageRecordTest.java (90%) create mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapterTest.java create mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapterTest.java create mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapterTest.java create mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/adapter/problem/ProblemAdapterTest.java rename src/test/java/kr/co/morandi/backend/{ => infrastructure}/config/AlgorithmInitializerTest.java (90%) rename src/test/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/algorithm/AlgorithmRepositoryTest.java (87%) rename src/test/java/kr/co/morandi/backend/{domain/defense => infrastructure/persistence}/customdefense/CustomDefenseRepositoryTest.java (77%) create mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepositoryTest.java rename src/test/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/defense/randomdefense/RandomDefenseRepositoryTest.java (89%) create mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java rename src/test/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/member/MemberRepositoryTest.java (88%) rename src/test/java/kr/co/morandi/backend/{domain => infrastructure/persistence}/problem/ProblemRepositoryTest.java (51%) create mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepositoryTest.java diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java b/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java new file mode 100644 index 00000000..c6c580d0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java @@ -0,0 +1,12 @@ +package kr.co.morandi.backend.application.port.out.defense.dailydefense; + +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; + +import java.time.LocalDate; + +public interface DailyDefensePort { + DailyDefense findDailyDefense(DefenseType defenseType, LocalDate date); + + DailyDefense saveDailyDefense(DailyDefense dailyDefense); +} diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java b/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java new file mode 100644 index 00000000..8f6cac87 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.application.port.out.defense.dailydefense; + +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.problem.model.Problem; + +import java.util.Map; + +public interface DailyDefenseProblemPort { + + Map getDailyDefenseProblem(Map criteria); +} diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPort.java b/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPort.java new file mode 100644 index 00000000..e2218137 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPort.java @@ -0,0 +1,13 @@ +package kr.co.morandi.backend.application.port.out.defensemanagement.defensesession; + +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.member.model.Member; + +import java.time.LocalDateTime; +import java.util.Optional; + +public interface DefenseSessionPort { + + DefenseSession saveDefenseSession(DefenseSession defenseSession); + Optional findTodaysDailyDefenseSession(Member member, LocalDateTime now); +} diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java b/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java new file mode 100644 index 00000000..ed0c10c9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java @@ -0,0 +1,10 @@ +package kr.co.morandi.backend.application.port.out.defensemanagement.sessiondetail; + +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; + +import java.util.Optional; + +public interface SessionDetailPort { + Optional findSessionDetail(DefenseSession defenseSession, Long problemId); +} diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/record/dailyrecord/DailyRecordPort.java b/src/main/java/kr/co/morandi/backend/application/port/out/record/dailyrecord/DailyRecordPort.java new file mode 100644 index 00000000..f8bb7bf9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/application/port/out/record/dailyrecord/DailyRecordPort.java @@ -0,0 +1,15 @@ +package kr.co.morandi.backend.application.port.out.record.dailyrecord; + +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; + +import java.time.LocalDate; +import java.util.Optional; + +public interface DailyRecordPort { + DailyRecord saveDailyRecord(DailyRecord dailyRecord); + Optional findDailyRecord(Member member, LocalDate date); + Optional findDailyRecord(Member member, Long recordId, LocalDate date); + + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java b/src/main/java/kr/co/morandi/backend/domain/algorithm/model/Algorithm.java similarity index 93% rename from src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java rename to src/main/java/kr/co/morandi/backend/domain/algorithm/model/Algorithm.java index d3c8b5c3..28f5b0bf 100644 --- a/src/main/java/kr/co/morandi/backend/domain/algorithm/Algorithm.java +++ b/src/main/java/kr/co/morandi/backend/domain/algorithm/model/Algorithm.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.algorithm; +package kr.co.morandi.backend.domain.algorithm.model; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java b/src/main/java/kr/co/morandi/backend/domain/bookmark/model/BookMark.java similarity index 84% rename from src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java rename to src/main/java/kr/co/morandi/backend/domain/bookmark/model/BookMark.java index 6bfc9f3d..a29201c3 100644 --- a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMark.java +++ b/src/main/java/kr/co/morandi/backend/domain/bookmark/model/BookMark.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.bookmark; +package kr.co.morandi.backend.domain.bookmark.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.member.model.Member; import lombok.*; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/model/ContentMemberLikes.java similarity index 85% rename from src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java rename to src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/model/ContentMemberLikes.java index 225010e0..4bfc0712 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikes.java +++ b/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/model/ContentMemberLikes.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.contentmemberlikes; +package kr.co.morandi.backend.domain.contentmemberlikes.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.member.model.Member; import lombok.AccessLevel; import lombok.Builder; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java b/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java index cd7ac2ca..601b597c 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java @@ -2,9 +2,16 @@ import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import lombok.*; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; +import kr.co.morandi.backend.domain.problem.model.Problem; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; +import java.time.LocalDateTime; +import java.util.Map; + @Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn @@ -19,8 +26,22 @@ public abstract class Defense extends BaseEntity { private String contentName; private Long attemptCount; - public Defense(String contentName) { + + @Enumerated(EnumType.STRING) + private DefenseType defenseType; + + public abstract LocalDateTime getEndTime(LocalDateTime startTime); + //팩토리 메소드 패턴 + public Map getDefenseProblems(ProblemGenerationService problemGenerationService) { + return problemGenerationService.getDefenseProblems(this); + } + public DefenseType getType() { + return defenseType; + } + protected Defense(String contentName, DefenseType defenseType) { this.contentName = contentName; this.attemptCount = 0L; + this.defenseType = defenseType; } + } diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java b/src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java new file mode 100644 index 00000000..fd28f244 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.domain.defense; + +public enum DefenseType { + DAILY, CUSTOM, STAGE, RANDOM + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefense.java similarity index 81% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefense.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefense.java index 7f49566e..2851af1b 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefense.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.defense.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -13,6 +13,9 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static kr.co.morandi.backend.domain.defense.DefenseType.CUSTOM; @Entity @DiscriminatorValue("CustomDefense") @@ -42,20 +45,11 @@ public class CustomDefense extends Defense { @OneToMany(mappedBy = "customDefense", cascade = CascadeType.ALL) private List customDefenseProblems = new ArrayList<>(); - private CustomDefense(List problems, Member member, String contentName, String description, - Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { - super(contentName); - this.problemCount = isValidProblemCount(problems.size()); - this.timeLimit = isValidTimeLimit(timeLimit); - this.description = description; - this.visibility = visibility; - this.defenseTier = defenseTier; - this.member = member; - this.customDefenseProblems = problems.stream() - .map(problem -> CustomDefenseProblem.create(this, problem)) - .toList(); - this.createDate = createDate; + @Override + public LocalDateTime getEndTime(LocalDateTime startTime) { + return startTime.plusMinutes(timeLimit); } + public static CustomDefense create(List problems, Member member, String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { return new CustomDefense(problems, member, contentName, description, visibility, defenseTier, timeLimit, createDate); } @@ -74,4 +68,19 @@ private int isValidProblemCount(int problemCount) { return problemCount; } + private CustomDefense(List problems, Member member, String contentName, String description, + Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { + super(contentName, CUSTOM); + this.problemCount = isValidProblemCount(problems.size()); + this.timeLimit = isValidTimeLimit(timeLimit); + this.description = description; + this.visibility = visibility; + this.defenseTier = defenseTier; + this.member = member; + AtomicLong problemNumber = new AtomicLong(1); + this.customDefenseProblems = problems.stream() + .map(problem -> CustomDefenseProblem.create(this, problemNumber.getAndIncrement(), problem)) + .toList(); + this.createDate = createDate; + } } diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblem.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefenseProblem.java similarity index 60% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblem.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefenseProblem.java index 055145db..23f07a16 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblem.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefenseProblem.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defense.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.problem.model.Problem; import lombok.*; @Entity @@ -23,20 +23,24 @@ public class CustomDefenseProblem extends BaseEntity { private Long solvedCount; - private CustomDefenseProblem(CustomDefense customDefense, Problem problem) { + private Long problemNumber; + + private CustomDefenseProblem(CustomDefense customDefense, Long problemNumber, Problem problem) { this.customDefense = customDefense; this.problem = problem; this.submitCount = 0L; + this.problemNumber = problemNumber; this.solvedCount = 0L; } + public static CustomDefenseProblem create(CustomDefense customDefense, Long problemNumber, Problem problem) { + return new CustomDefenseProblem(customDefense, problemNumber, problem); + } @Builder - private CustomDefenseProblem(CustomDefense customDefense, Problem problem, Long submitCount, Long solvedCount) { + private CustomDefenseProblem(CustomDefense customDefense, Long problemNumber, Problem problem, Long submitCount, Long solvedCount) { this.customDefense = customDefense; this.problem = problem; this.submitCount = submitCount; + this.problemNumber = problemNumber; this.solvedCount = solvedCount; } - public static CustomDefenseProblem create(CustomDefense customDefense, Problem problem) { - return new CustomDefenseProblem(customDefense, problem); - } } diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/DefenseTier.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/DefenseTier.java similarity index 78% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/DefenseTier.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/DefenseTier.java index 8b427a89..0165715a 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/DefenseTier.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/DefenseTier.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.defense.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense.model; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/Visibility.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/Visibility.java similarity index 74% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/Visibility.java rename to src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/Visibility.java index 6e4f831f..17a5c2f0 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/Visibility.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/Visibility.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.defense.customdefense; +package kr.co.morandi.backend.domain.defense.customdefense.model; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java new file mode 100644 index 00000000..17a1fdad --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java @@ -0,0 +1,24 @@ +package kr.co.morandi.backend.domain.defense.customdefense.service; + +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.ProblemGenerationStrategy; +import kr.co.morandi.backend.domain.problem.model.Problem; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static kr.co.morandi.backend.domain.defense.DefenseType.CUSTOM; + +@Component +public class CustomDefenseStrategy implements ProblemGenerationStrategy { + + @Override + public Map generateDefenseProblems(Defense defense) { + return null; + } + @Override + public DefenseType getDefenseType() { + return CUSTOM; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java deleted file mode 100644 index 88f9a5ca..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefense.java +++ /dev/null @@ -1,44 +0,0 @@ -package kr.co.morandi.backend.domain.defense.dailydefense; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.OneToMany; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.problem.Problem; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Entity -@DiscriminatorValue("DailyDefense") -@Getter -@SuperBuilder -@AllArgsConstructor -@NoArgsConstructor -public class DailyDefense extends Defense { - - private LocalDateTime date; - - private Integer problemCount; - - @OneToMany(mappedBy = "DailyDefense", cascade = CascadeType.ALL) - List DailyDefenseProblems = new ArrayList<>(); - - private DailyDefense(LocalDateTime date, String contentName, List problems) { - super(contentName); - this.date = date; - this.DailyDefenseProblems = problems.stream() - .map(problem -> DailyDefenseProblem.create(this, problem)) - .toList(); - this.problemCount = problems.size(); - } - public static DailyDefense create(LocalDateTime date, String contentName, List problems) { - return new DailyDefense(date, contentName, problems); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java deleted file mode 100644 index 368475ac..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.defense.dailydefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DailyDefenseProblemRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java deleted file mode 100644 index 9837eea4..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.domain.defense.dailydefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DailyDefenseRepository extends JpaRepository { - -} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefense.java new file mode 100644 index 00000000..c44cabfa --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefense.java @@ -0,0 +1,70 @@ +package kr.co.morandi.backend.domain.defense.dailydefense.model; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.OneToMany; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; +import kr.co.morandi.backend.domain.problem.model.Problem; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; + +@Entity +@DiscriminatorValue("DailyDefense") +@Getter +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +public class DailyDefense extends Defense { + + private LocalDate date; + + private Integer problemCount; + + @Builder.Default + @OneToMany(mappedBy = "dailyDefense", cascade = CascadeType.ALL) + List dailyDefenseProblems = new ArrayList<>(); + + @Override + public LocalDateTime getEndTime(LocalDateTime startTime) { + //시작 날까지 + return date.atStartOfDay().plusDays(1).minusSeconds(1); + } + public Map getTryingProblem(Long problemNumber, ProblemGenerationService problemGenerationService) { + Map tryProblem = this.getDefenseProblems(problemGenerationService).entrySet().stream() + .filter(p -> p.getKey().equals(problemNumber)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (tryProblem.isEmpty()) { + throw new IllegalArgumentException("해당 문제가 오늘의 문제 목록에 없습니다."); + } + return tryProblem; + } + + private DailyDefense(LocalDate date, String contentName, Map problems) { + super(contentName, DAILY); + this.date = date; + this.dailyDefenseProblems = problems.entrySet().stream() + .map(problem -> DailyDefenseProblem.create(this, problem.getValue(), problem.getKey())) + .toList(); + + this.problemCount = problems.size(); + } + public static DailyDefense create(LocalDate date, String contentName, Map problems) { + return new DailyDefense(date, contentName, problems); + } + + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblem.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefenseProblem.java similarity index 59% rename from src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblem.java rename to src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefenseProblem.java index 49b215fb..7b230972 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblem.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefenseProblem.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defense.dailydefense; +package kr.co.morandi.backend.domain.defense.dailydefense.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.problem.model.Problem; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -20,8 +20,10 @@ public class DailyDefenseProblem extends BaseEntity { private Long solvedCount; + private Long problemNumber; + @ManyToOne(fetch = FetchType.LAZY) - private DailyDefense DailyDefense; + private DailyDefense dailyDefense; @ManyToOne(fetch = FetchType.LAZY) private Problem problem; @@ -31,13 +33,14 @@ public class DailyDefenseProblem extends BaseEntity { private static final Long INITIAL_SOLVED_COUNT = 0L; @Builder - private DailyDefenseProblem(DailyDefense DailyDefense, Problem problem) { - this.DailyDefense = DailyDefense; + private DailyDefenseProblem(DailyDefense dailyDefense, Problem problem, Long problemNumber) { + this.dailyDefense = dailyDefense; this.problem = problem; this.submitCount = INITIAL_SUBMIT_COUNT; this.solvedCount = INITIAL_SOLVED_COUNT; + this.problemNumber = problemNumber; } - public static DailyDefenseProblem create(DailyDefense DailyDefense, Problem problem) { - return new DailyDefenseProblem(DailyDefense, problem); + public static DailyDefenseProblem create(DailyDefense dailyDefense, Problem problem, Long problemNumber) { + return new DailyDefenseProblem(dailyDefense, problem, problemNumber); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationService.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationService.java new file mode 100644 index 00000000..cddfe8c8 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationService.java @@ -0,0 +1,62 @@ +package kr.co.morandi.backend.domain.defense.dailydefense.service; + +import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefenseProblemPort; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; +import kr.co.morandi.backend.domain.problem.model.Problem; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Map; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; + +@Service +@RequiredArgsConstructor +public class DailyDefenseGenerationService { + + private final DailyDefenseProblemPort dailyDefenseProblemPort; + private final DailyDefensePort dailyDefensePort; + + private static final Map.Entry PROBLEM_1 = getRandomCriteria(1L, B5, B1, 1000L, 300000L); + private static final Map.Entry PROBLEM_2 = getRandomCriteria(2L, S5, S4, 1000L, 300000L); + private static final Map.Entry PROBLEM_3 = getRandomCriteria(3L, S3, S1, 1000L, 300000L); + private static final Map.Entry PROBLEM_4 = getRandomCriteria(4L, G5, G4, 1000L, 300000L); + private static final Map.Entry PROBLEM_5 = getRandomCriteria(5L, G3, G1, 1000L, 300000L); + private static final String POSTFIX = "%d월 %d일 오늘의 문제"; + + @Transactional + public boolean generateDailyDefense(LocalDateTime requestTime) { + final Map request = Map.ofEntries(PROBLEM_1, PROBLEM_2, PROBLEM_3, PROBLEM_4, PROBLEM_5); + + final Map dailyDefenseProblem = dailyDefenseProblemPort.getDailyDefenseProblem(request); + + final LocalDate targetDate = requestTime.plusDays(1L).toLocalDate(); + + final DailyDefense dailyDefense = DailyDefense.create(targetDate, + String.format(POSTFIX, targetDate.getMonthValue(), targetDate.getDayOfMonth()), dailyDefenseProblem); + + dailyDefensePort.saveDailyDefense(dailyDefense); + + return true; + } + + private static Map.Entry getRandomCriteria(Long problemNumber, + ProblemTier startTier, + ProblemTier endTier, + Long minSolvedCount, + Long maxSolvedCount) { + + return Map.entry(problemNumber, RandomCriteria.builder() + .minSolvedCount(minSolvedCount) + .maxSolvedCount(maxSolvedCount) + .difficultyRange(RandomCriteria.DifficultyRange.of(startTier, endTier)) + .build()); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseStrategy.java b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseStrategy.java new file mode 100644 index 00000000..7c33926f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseStrategy.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.domain.defense.dailydefense.service; + +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.ProblemGenerationStrategy; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.domain.problem.model.Problem; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; + +@Component +@RequiredArgsConstructor +public class DailyDefenseStrategy implements ProblemGenerationStrategy { + + //TODO 여기도 Port로 바꿔야함 + private final DailyDefenseProblemRepository dailyDefenseProblemRepository; + @Override + public Map generateDefenseProblems(Defense defense) { + final List defenseProblems = dailyDefenseProblemRepository.findAllProblemsContainsDefenseId(defense.getDefenseId()); + return defenseProblems.stream() + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + @Override + public DefenseType getDefenseType() { + return DAILY; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java b/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java new file mode 100644 index 00000000..846cdc03 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java @@ -0,0 +1,14 @@ +package kr.co.morandi.backend.domain.defense.problemgenerationstrategy; + +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.problem.model.Problem; + +import java.util.Map; + +public interface ProblemGenerationStrategy { + + Map generateDefenseProblems(Defense defense); + DefenseType getDefenseType(); + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/service/ProblemGenerationService.java b/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/service/ProblemGenerationService.java new file mode 100644 index 00000000..c4f5c3ef --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/service/ProblemGenerationService.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service; + +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.ProblemGenerationStrategy; +import kr.co.morandi.backend.domain.problem.model.Problem; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class ProblemGenerationService { + + private final Map strategies; + + public ProblemGenerationService(List strategies) { + this.strategies = strategies.stream() + .collect(Collectors.toMap(ProblemGenerationStrategy::getDefenseType, strategy -> strategy)); + } + public Map getDefenseProblems(Defense defense) { + return strategies.get(defense.getType()).generateDefenseProblems(defense); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/random/model/RandomDefense.java similarity index 77% rename from src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefense.java rename to src/main/java/kr/co/morandi/backend/domain/defense/random/model/RandomDefense.java index c7ac007b..e6b07439 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/random/model/RandomDefense.java @@ -1,11 +1,15 @@ -package kr.co.morandi.backend.domain.defense.random; +package kr.co.morandi.backend.domain.defense.random.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; import lombok.*; import lombok.experimental.SuperBuilder; +import java.time.LocalDateTime; + +import static kr.co.morandi.backend.domain.defense.DefenseType.RANDOM; + @Entity @DiscriminatorValue("RandomDefense") @Getter @@ -19,8 +23,14 @@ public class RandomDefense extends Defense { private Integer problemCount; private Long timeLimit; + + @Override + public LocalDateTime getEndTime(LocalDateTime startTime) { + return startTime.plusMinutes(timeLimit); + } + private RandomDefense(RandomCriteria randomCriteria, Integer problemCount, Long timeLimit, String contentName) { - super(contentName); + super(contentName, RANDOM); this.randomCriteria = randomCriteria; this.problemCount = isValidProblemCount(problemCount); this.timeLimit = isValidTimeLimit(timeLimit); diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/random/randomcriteria/RandomCriteria.java b/src/main/java/kr/co/morandi/backend/domain/defense/random/model/randomcriteria/RandomCriteria.java similarity index 94% rename from src/main/java/kr/co/morandi/backend/domain/defense/random/randomcriteria/RandomCriteria.java rename to src/main/java/kr/co/morandi/backend/domain/defense/random/model/randomcriteria/RandomCriteria.java index 602f91b0..a526338b 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/random/randomcriteria/RandomCriteria.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/random/model/randomcriteria/RandomCriteria.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defense.random.randomcriteria; +package kr.co.morandi.backend.domain.defense.random.model.randomcriteria; import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; -import kr.co.morandi.backend.domain.defense.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java b/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java deleted file mode 100644 index 318b27cb..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.defense.stagedefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface StageDefenseRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefense.java b/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/model/StageDefense.java similarity index 71% rename from src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefense.java rename to src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/model/StageDefense.java index fa4fcdc7..caf87330 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefense.java +++ b/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/model/StageDefense.java @@ -1,11 +1,15 @@ -package kr.co.morandi.backend.domain.defense.stagedefense; +package kr.co.morandi.backend.domain.defense.stagedefense.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; import lombok.*; import lombok.experimental.SuperBuilder; +import java.time.LocalDateTime; + +import static kr.co.morandi.backend.domain.defense.DefenseType.STAGE; + @Entity @DiscriminatorValue("StageDefense") @Getter @@ -19,19 +23,28 @@ public class StageDefense extends Defense { private Double averageStage; private Long timeLimit; - private StageDefense(RandomCriteria randomCriteria, Long timeLimit, String contentName) { - super(contentName); - this.randomCriteria = randomCriteria; - this.averageStage = 0.0; - this.timeLimit = isValidTimeLimit(timeLimit); + + @Override + public LocalDateTime getEndTime(LocalDateTime startTime) { + return startTime.plusMinutes(timeLimit); } + public static StageDefense create(RandomCriteria randomCriteria, Long timeLimit, String contentName) { return new StageDefense(randomCriteria, timeLimit, contentName); } + private Long isValidTimeLimit(Long timeLimit) { if (timeLimit <= 0) { throw new IllegalArgumentException("스테이지 모드 제한 시간은 0보다 커야 합니다."); } return timeLimit; } + + private StageDefense(RandomCriteria randomCriteria, Long timeLimit, String contentName) { + super(contentName, STAGE); + this.randomCriteria = randomCriteria; + this.averageStage = 0.0; + this.timeLimit = isValidTimeLimit(timeLimit); + } + } diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/tier/ProblemTier.java b/src/main/java/kr/co/morandi/backend/domain/defense/tier/ProblemTier.java deleted file mode 100644 index 86d4ab31..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/tier/ProblemTier.java +++ /dev/null @@ -1,18 +0,0 @@ -package kr.co.morandi.backend.domain.defense.tier; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ProblemTier { - UNRANKED(0), - B5(1),B4(2),B3(3),B2(4),B1(5), - S5(6),S4(7),S3(8),S2(9),S1(10), - G5(11),G4(12),G3(13),G2(14),G1(15), - P5(16),P4(17),P3(18),P2(19),P1(20), - D5(21),D4(22),D3(23),D2(24),D1(25), - R5(26),R4(27),R3(28),R2(29),R1(30); - - private final int tier; -} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/tier/model/ProblemTier.java b/src/main/java/kr/co/morandi/backend/domain/defense/tier/model/ProblemTier.java new file mode 100644 index 00000000..2b9cdf91 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defense/tier/model/ProblemTier.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.domain.defense.tier.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +@RequiredArgsConstructor +public enum ProblemTier { + UNRANKED(0), + B5(1),B4(2),B3(3),B2(4),B1(5), + S5(6),S4(7),S3(8),S2(9),S1(10), + G5(11),G4(12),G3(13),G2(14),G1(15), + P5(16),P4(17),P3(18),P2(19),P1(20), + D5(21),D4(22),D3(23),D2(24),D1(25), + R5(26),R4(27),R3(28),R2(29),R1(30); + + private final int tier; + + private static final List VALUES = Arrays.asList(values()); + + public static List tierRangeOf(ProblemTier start, ProblemTier end) { + if (start.tier > end.tier) + throw new IllegalArgumentException("시작 티어가 끝 티어보다 높을 수 없습니다."); + + return VALUES.stream() + .filter(tier -> tier.tier >= start.tier && tier.tier <= end.tier) + .toList(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/request/StartDailyDefenseServiceRequest.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/request/StartDailyDefenseServiceRequest.java new file mode 100644 index 00000000..b9c89c33 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/request/StartDailyDefenseServiceRequest.java @@ -0,0 +1,20 @@ +package kr.co.morandi.backend.domain.defensemanagement.management.request; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class StartDailyDefenseServiceRequest { + + private Long problemNumber; + + @Builder + private StartDailyDefenseServiceRequest(LocalDateTime requestDateTime, Long problemNumber) { + this.problemNumber = problemNumber; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/DefenseProblemResponse.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/DefenseProblemResponse.java new file mode 100644 index 00000000..4b063a7d --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/DefenseProblemResponse.java @@ -0,0 +1,63 @@ +package kr.co.morandi.backend.domain.defensemanagement.management.response; + +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; +import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language; +import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DefenseProblemResponse { + + private Long problemId; + private Long problemNumber; + private Long baekjoonProblemId; + private boolean isCorrect; + private Language tempCodeLanguage; + private String tempCode; + + + public static List fromDailyDefense(Map tryProblem, DefenseSession defenseSession, DailyRecord dailyRecord) { + return tryProblem.entrySet().stream() + .map(entry -> { + final Long problemNumber = entry.getKey(); + final Problem problem = entry.getValue(); + final boolean isCorrect = dailyRecord.isSolvedProblem(problemNumber); + + final SessionDetail sessionDetail = defenseSession.getSessionDetail(problemNumber); + + final Language lastAccessLanguage = sessionDetail.getLastAccessLanguage(); + final TempCode tempCode = sessionDetail.getTempCode(lastAccessLanguage); + + return DefenseProblemResponse.builder() + .problemId(problem.getProblemId()) + .baekjoonProblemId(problem.getBaekjoonProblemId()) + .problemNumber(problemNumber) + .isCorrect(isCorrect) + .tempCode(tempCode.getCode()) + .tempCodeLanguage(lastAccessLanguage) + .build(); + + }) + .toList(); + } + @Builder + private DefenseProblemResponse(Long problemId, Long problemNumber, Long baekjoonProblemId, boolean isCorrect, + Language tempCodeLanguage, String tempCode) { + this.problemId = problemId; + this.problemNumber = problemNumber; + this.baekjoonProblemId = baekjoonProblemId; + this.isCorrect = isCorrect; + this.tempCodeLanguage = tempCodeLanguage; + this.tempCode = tempCode; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/StartDailyDefenseServiceResponse.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/StartDailyDefenseServiceResponse.java new file mode 100644 index 00000000..14ff0e17 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/StartDailyDefenseServiceResponse.java @@ -0,0 +1,52 @@ +package kr.co.morandi.backend.domain.defensemanagement.management.response; + +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class StartDailyDefenseServiceResponse { + + private Long defenseSessionId; + private String contentName; + private DefenseType defenseType; + private LocalDateTime lastAccessTime; + private List defenseProblems; + + public static StartDailyDefenseServiceResponse from(Map tryProblem, + DailyDefense dailyDefense, + DefenseSession defenseSession, + DailyRecord dailyRecord) { + return StartDailyDefenseServiceResponse.builder() + .defenseSessionId(defenseSession.getDefenseSessionId()) + .contentName(dailyDefense.getContentName()) + .defenseType(dailyDefense.getDefenseType()) + .lastAccessTime(defenseSession.getLastAccessDateTime()) + .defenseProblems(DefenseProblemResponse.fromDailyDefense(tryProblem, defenseSession, dailyRecord)) + .build(); + } + + @Builder + private StartDailyDefenseServiceResponse(Long defenseSessionId, + String contentName, + DefenseType defenseType, + LocalDateTime lastAccessTime, + List defenseProblems) { + this.defenseSessionId = defenseSessionId; + this.defenseType = defenseType; + this.contentName = contentName; + this.lastAccessTime = lastAccessTime; + this.defenseProblems = defenseProblems; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementService.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementService.java new file mode 100644 index 00000000..6fd929e3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementService.java @@ -0,0 +1,74 @@ +package kr.co.morandi.backend.domain.defensemanagement.management.service; + +import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.application.port.out.defensemanagement.defensesession.DefenseSessionPort; +import kr.co.morandi.backend.application.port.out.record.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; +import kr.co.morandi.backend.domain.defensemanagement.management.request.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.domain.defensemanagement.management.response.StartDailyDefenseServiceResponse; +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class DailyDefenseManagementService { + + private final DailyDefensePort dailyDefensePort; + private final DailyRecordPort dailyRecordPort; + private final ProblemGenerationService problemGenerationService; + private final DefenseSessionPort defenseSessionPort; + + @Transactional + public StartDailyDefenseServiceResponse startDailyDefense(StartDailyDefenseServiceRequest request, Member member, LocalDateTime requestTime) { + Long problemNumber = request.getProblemNumber(); + + // 세션이랑 세션 Detail을 찾아서 응시 기록이 있는지 살펴보기 + final Optional maybeDefenseSession = defenseSessionPort.findTodaysDailyDefenseSession(member, requestTime); + + // 오늘의 Defense를 찾아오기 + final DailyDefense dailyDefense = dailyDefensePort.findDailyDefense(DAILY, requestTime.toLocalDate()); + + // 오늘의 문제 목록 중에서 원하는 문제를 찾아서 시도하려는 문제 목록에 추가 (오늘의 문제 목록에 해당 문제가 없으면 예외 발생) + final Map tryProblem = dailyDefense.getTryingProblem(problemNumber, problemGenerationService); + + // DefenseSession이 있으면 get, 없으면 새로운 DefenseSession을 생성 + final DefenseSession defenseSession = maybeDefenseSession.orElseGet(() -> createNewSession(member, requestTime, dailyDefense, tryProblem)); + + // DefenseSession의 recordId로 DailyRecord를 찾고 문제를 시도했는지 확인하고 시도하지 않았으면 시도하도록 함 + Long recordId = defenseSession.getRecordId(); + DailyRecord dailyRecord = dailyRecordPort.findDailyRecord(member, recordId, requestTime.toLocalDate()) + .orElseThrow(() -> new IllegalArgumentException("세션에 해당하는 응시 기록이 없습니다.")); + + if (!defenseSession.hasTriedProblem(problemNumber)) { + dailyRecord.tryMoreProblem(tryProblem); + defenseSession.tryMoreProblem(problemNumber, requestTime); + } + + final DefenseSession savedDefenseSession = defenseSessionPort.saveDefenseSession(defenseSession); + + // 문제 목록을 DefenseProblemResponse DTO로 변환 + return StartDailyDefenseServiceResponse.from(tryProblem, dailyDefense, savedDefenseSession, dailyRecord); + } + private DefenseSession createNewSession(Member member, LocalDateTime now, DailyDefense dailyDefense, Map tryProblem) { + DailyRecord dailyRecord = DailyRecord.tryDefense(now, dailyDefense, member, tryProblem); + DailyRecord savedDailyRecord = dailyRecordPort.saveDailyRecord(dailyRecord); + Long recordId = savedDailyRecord.getRecordId(); + + return DefenseSession.startSession(member, recordId, dailyDefense.getDefenseType(), tryProblem.keySet(), now, dailyDefense.getEndTime(now)); + } + + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSession.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSession.java new file mode 100644 index 00000000..ff1e46e3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSession.java @@ -0,0 +1,101 @@ +package kr.co.morandi.backend.domain.defensemanagement.session.model; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defensemanagement.session.model.ExamStatus.COMPLETED; +import static kr.co.morandi.backend.domain.defensemanagement.session.model.ExamStatus.IN_PROGRESS; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DefenseSession extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long defenseSessionId; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + private Long recordId; + + @OneToMany(mappedBy = "defenseSession", cascade = CascadeType.ALL) + private List sessionDetails = new ArrayList<>(); + + private LocalDateTime startDateTime; + + private LocalDateTime endDateTime; + + private Long lastAccessProblemNumber; + + private LocalDateTime lastAccessDateTime; + + @Enumerated(EnumType.STRING) + private DefenseType defenseType; + + @Enumerated(EnumType.STRING) + private ExamStatus examStatus; + + private static final Long INITIAL_ACCESS_PROBLEM_NUMBER = 1L; + + public SessionDetail getSessionDetail(Long problemNumber) { + return getSessionDetails().stream() + .filter(detail -> Objects.equals(detail.getProblemNumber(), problemNumber)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 문제가 없습니다.")); + } + public boolean hasTriedProblem(Long problemNumber) { + return getSessionDetails().stream() + .anyMatch(detail -> Objects.equals(detail.getProblemNumber(), problemNumber)); + } + public void tryMoreProblem(Long problemNumber, LocalDateTime accessDateTime) { + if (examStatus == COMPLETED || accessDateTime.isAfter(endDateTime)) { + throw new IllegalStateException("이미 종료된 시험입니다."); + } + // 이미 있는 시험이라면 + if (sessionDetails.stream() + .anyMatch(sessionDetail -> sessionDetail.getProblemNumber().equals(problemNumber))) { + return ; + } + sessionDetails.add(SessionDetail.create(this, problemNumber)); + lastAccessProblemNumber = problemNumber; + lastAccessDateTime = accessDateTime; + + } + public static DefenseSession startSession(Member member, Long recordId, DefenseType defenseType, Set problemNumbers, + LocalDateTime startDateTime, LocalDateTime endDateTime) { + return new DefenseSession(member, recordId, defenseType, problemNumbers, startDateTime, endDateTime); + } + private DefenseSession(Member member, Long recordId, DefenseType defenseType, Set problemNumbers, + LocalDateTime startDateTime, LocalDateTime endDateTime) { + if(problemNumbers==null || problemNumbers.isEmpty()) + throw new IllegalArgumentException("문제 번호가 없습니다."); + this.member = member; + this.recordId = recordId; + this.defenseType = defenseType; + this.sessionDetails = problemNumbers.stream() + .map(problemNumber -> SessionDetail.create(this, problemNumber)) + .collect(Collectors.toCollection(ArrayList::new)); + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.examStatus = IN_PROGRESS; + this.lastAccessDateTime = startDateTime; + this.lastAccessProblemNumber = problemNumbers.stream() + .findFirst() + .orElse(INITIAL_ACCESS_PROBLEM_NUMBER); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/ExamStatus.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/ExamStatus.java new file mode 100644 index 00000000..382d844b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/ExamStatus.java @@ -0,0 +1,5 @@ +package kr.co.morandi.backend.domain.defensemanagement.session.model; + +public enum ExamStatus { + IN_PROGRESS, COMPLETED, STAGE_PROGRESS; +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetail.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetail.java new file mode 100644 index 00000000..e56d7ca3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetail.java @@ -0,0 +1,85 @@ +package kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language; +import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SessionDetail extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long sessionDetailId; + + @ManyToOne(fetch = FetchType.LAZY) + private DefenseSession defenseSession; + + @OneToMany(mappedBy = "sessionDetail", cascade = CascadeType.ALL) + private Set tempCodes = new HashSet<>(); + + private Long problemNumber; + + private Language lastAccessLanguage; + + public static final Language INITIAL_LANGUAGE = Language.CPP; + public static SessionDetail create(DefenseSession defenseSession, Long problemNumber) { + return new SessionDetail(defenseSession, problemNumber); + } + public TempCode getTempCode(Language language) { + Optional maybeTempCode = getTempCodes().stream() + .filter(tempcode -> tempcode.getLanguage().equals(language)) + .findFirst(); + + return maybeTempCode.orElseGet(() -> addTempCode(language, language.getInitialCode())); + } + + public TempCode addTempCode(Language language, String code) { + TempCode tempCode = TempCode.create(language, code, this); + getTempCodes().add(tempCode); + + return tempCode; + } + + /* + * 만약 없는 언어로 tempCode를 update하려고 했더라도 + * addTempCode를 호출해서 추가하고, 예외를 반환하지 않는다. + * */ + public void updateTempCode(Language language, String code) { + this.lastAccessLanguage = language; + final Optional tempCode = getTempCodes().stream() + .filter(tempcode -> tempcode.getLanguage().equals(language)) + .findFirst(); + + if(tempCode.isPresent()) { + tempCode.get().updateTempCode(code); + return; + } + // 만약 없는 언어로 tempCode를 update하려고 했으면 addTempCode를 호출해서 추가해준다. + addTempCode(language, code); + } + + private SessionDetail(DefenseSession defenseSession, Long problemNumber) { + this.defenseSession = defenseSession; + this.problemNumber = problemNumber; + this.lastAccessLanguage = INITIAL_LANGUAGE; + this.tempCodes.add(TempCode.create(INITIAL_LANGUAGE, INITIAL_LANGUAGE.getInitialCode(), this)); + } + @Builder + private SessionDetail(DefenseSession defenseSession, Set tempCodes, Long problemNumber, Language lastAccessLanguage) { + this.defenseSession = defenseSession; + this.tempCodes = tempCodes; + this.problemNumber = problemNumber; + this.lastAccessLanguage = lastAccessLanguage; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/Language.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/Language.java new file mode 100644 index 00000000..2ab46983 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/Language.java @@ -0,0 +1,30 @@ +package kr.co.morandi.backend.domain.defensemanagement.tempcode.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum Language { + JAVA(""" + public class Main { + public static void main(String[] args) { + System.out.println("Hello World"); + } + } + """), + CPP(""" + #include + using namespace std; + + int main() { + cout << "Hello World" << endl; + return 0; + } + """), + PYTHON(""" + print("Hello World") + """); + + private final String initialCode; +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/TempCode.java b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/TempCode.java new file mode 100644 index 00000000..98de3fb7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/TempCode.java @@ -0,0 +1,60 @@ +package kr.co.morandi.backend.domain.defensemanagement.tempcode.model; + +import jakarta.persistence.*; +import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class TempCode extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long tempCodeId; + + @ManyToOne(fetch = FetchType.LAZY) + private SessionDetail sessionDetail; + + @Enumerated(EnumType.STRING) + private Language language; + + @Column(columnDefinition = "TEXT") + private String code; + + public static TempCode create(Language language, String code, SessionDetail sessionDetail) { + return TempCode.builder() + .language(language) + .code(code) + .sessionDetail(sessionDetail) + .build(); + } + + public void updateTempCode(String code) { + this.code = code; + } + + @Builder + private TempCode(SessionDetail sessionDetail, Language language, String code) { + this.sessionDetail = sessionDetail; + this.language = language; + this.code = code; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TempCode tempCode = (TempCode) o; + + return getLanguage() == tempCode.getLanguage(); + } + @Override + public int hashCode() { + return getLanguage() != null ? getLanguage().hashCode() : 0; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java b/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java index 9333eb5a..26426ca5 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java +++ b/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java @@ -3,8 +3,8 @@ import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; @@ -32,7 +32,7 @@ public abstract class Detail extends BaseEntity { private Defense defense; @ManyToOne(fetch = FetchType.LAZY) - private Record record; + private Record record; @ManyToOne(fetch = FetchType.LAZY) private Member member; @@ -43,13 +43,12 @@ public abstract class Detail extends BaseEntity { private static final Long INITIAL_SUBMIT_COUNT = 0L; private static final Boolean INITIAL_IS_SOLVED = false; - protected Detail(Member member, Problem problem, - Record record, Defense defense) { + protected Detail(Member member, Problem problem, Record records, Defense defense) { this.isSolved = INITIAL_IS_SOLVED; this.submitCount = INITIAL_SUBMIT_COUNT; this.solvedCode = null; this.defense = defense; - this.record = record; + this.record = records; this.member = member; this.problem = problem; } diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java b/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java deleted file mode 100644 index 7fae3b37..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.detail.customdefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CustomDetailRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/model/CustomDetail.java similarity index 51% rename from src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetail.java rename to src/main/java/kr/co/morandi/backend/domain/detail/customdefense/model/CustomDetail.java index 4d02888b..ccaeb16b 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetail.java +++ b/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/model/CustomDetail.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.detail.customdefense; +package kr.co.morandi.backend.domain.detail.customdefense.model; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; @@ -19,15 +19,16 @@ @DiscriminatorValue("CustomDefenseProblemRecord") public class CustomDetail extends Detail { + private Long problemNumber; private Long solvedTime; private static final long INITIAL_SOLVED_TIME = 0L; - private CustomDetail(Member member, Problem problem, Record record, Defense defense) { - super(member, problem, record, defense); + private CustomDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + super(member, problem, records, defense); + this.problemNumber = sequenceNumber; this.solvedTime = INITIAL_SOLVED_TIME; } - public static CustomDetail create(Member member, Problem problem, - Record record, Defense defense) { - return new CustomDetail(member, problem, record, defense); + public static CustomDetail create(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + return new CustomDetail(member, sequenceNumber, problem, records, defense); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java deleted file mode 100644 index d18d436c..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetail.java +++ /dev/null @@ -1,27 +0,0 @@ -package kr.co.morandi.backend.domain.detail.dailydefense; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.record.Record; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -@Entity -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DiscriminatorValue("DailyDefenseProblemRecord") -public class DailyDetail extends Detail { - - private DailyDetail(Member member, Problem problem, Record record, Defense defense) { - super(member, problem, record, defense); - } - public static DailyDetail create(Member member, Problem problem, - Record record, Defense defense) { - return new DailyDetail(member, problem, record, defense); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/model/DailyDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/model/DailyDetail.java new file mode 100644 index 00000000..f85c3551 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/model/DailyDetail.java @@ -0,0 +1,31 @@ +package kr.co.morandi.backend.domain.detail.dailydefense.model; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@SuperBuilder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("DailyDefenseProblemRecord") +public class DailyDetail extends Detail { + + Long problemNumber; + + private DailyDetail(Member member, Long problemNumber, Problem problem, Record records, Defense defense) { + super(member, problem, records, defense); + this.problemNumber = problemNumber; + } + public static DailyDetail create(Member member, Long problemNumber, Problem problem, Record records, Defense defense) { + return new DailyDetail(member, problemNumber, problem, records, defense); + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/model/RandomDetail.java similarity index 51% rename from src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetail.java rename to src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/model/RandomDetail.java index 03c04e03..b8e66ad6 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetail.java +++ b/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/model/RandomDetail.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.detail.randomdefense; +package kr.co.morandi.backend.domain.detail.randomdefense.model; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; @@ -19,17 +19,17 @@ @DiscriminatorValue("RandomDefenseProblemRecord") public class RandomDetail extends Detail { + private Long problemNumber; private Long solvedTime; private static final long INITIAL_SOLVED_TIME = 0L; - private RandomDetail(Member member, Problem problem, - Record record, Defense defense) { - super(member, problem, record, defense); + private RandomDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + super(member, problem, records, defense); + this.problemNumber = sequenceNumber; this.solvedTime = INITIAL_SOLVED_TIME; } - public static RandomDetail create(Member member, Problem problem, - Record record, Defense defense) { - return new RandomDetail(member, problem, record, defense); + public static RandomDetail create(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + return new RandomDetail(member, sequenceNumber, problem, records, defense); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/StageDetail.java b/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/model/StageDetail.java similarity index 52% rename from src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/StageDetail.java rename to src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/model/StageDetail.java index fadc3485..a6c97bfe 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/StageDetail.java +++ b/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/model/StageDetail.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.detail.stagedefense; +package kr.co.morandi.backend.domain.detail.stagedefense.model; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; @@ -21,14 +21,12 @@ public class StageDetail extends Detail { private Long solvedTime; private Long stageNumber; - private StageDetail(Long stageNumber, Member member, Problem problem, - Record record, Defense defense) { - super(member, problem, record, defense); + private StageDetail(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { + super(member, problem, records, defense); this.solvedTime = 0L; this.stageNumber = stageNumber; } - public static StageDetail create(Long stageNumber, Member member, Problem problem, - Record record, Defense defense) { - return new StageDetail(stageNumber, member, problem, record, defense); + public static StageDetail create(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { + return new StageDetail(member, stageNumber, problem, records, defense); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/submit/SubmitCode.java b/src/main/java/kr/co/morandi/backend/domain/detail/submit/model/SubmitCode.java similarity index 86% rename from src/main/java/kr/co/morandi/backend/domain/detail/submit/SubmitCode.java rename to src/main/java/kr/co/morandi/backend/domain/detail/submit/model/SubmitCode.java index 2c188195..de2ccb61 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/submit/SubmitCode.java +++ b/src/main/java/kr/co/morandi/backend/domain/detail/submit/model/SubmitCode.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.detail.submit; +package kr.co.morandi.backend.domain.detail.submit.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.member.Member; +import kr.co.morandi.backend.domain.member.model.Member; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/member/Member.java b/src/main/java/kr/co/morandi/backend/domain/member/model/Member.java similarity index 96% rename from src/main/java/kr/co/morandi/backend/domain/member/Member.java rename to src/main/java/kr/co/morandi/backend/domain/member/model/Member.java index c0638443..5bb971d3 100644 --- a/src/main/java/kr/co/morandi/backend/domain/member/Member.java +++ b/src/main/java/kr/co/morandi/backend/domain/member/model/Member.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.member; +package kr.co.morandi.backend.domain.member.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; diff --git a/src/main/java/kr/co/morandi/backend/domain/member/SocialType.java b/src/main/java/kr/co/morandi/backend/domain/member/model/SocialType.java similarity index 81% rename from src/main/java/kr/co/morandi/backend/domain/member/SocialType.java rename to src/main/java/kr/co/morandi/backend/domain/member/model/SocialType.java index ebfcaefd..20883cd6 100644 --- a/src/main/java/kr/co/morandi/backend/domain/member/SocialType.java +++ b/src/main/java/kr/co/morandi/backend/domain/member/model/SocialType.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.member; +package kr.co.morandi.backend.domain.member.model; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java b/src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java deleted file mode 100644 index 9d4e87a0..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/problem/ProblemRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package kr.co.morandi.backend.domain.problem; - -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface ProblemRepository extends JpaRepository { - - List findAllByProblemStatus(ProblemStatus problemStatus); -} - diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java b/src/main/java/kr/co/morandi/backend/domain/problem/model/Problem.java similarity index 86% rename from src/main/java/kr/co/morandi/backend/domain/problem/Problem.java rename to src/main/java/kr/co/morandi/backend/domain/problem/model/Problem.java index 5667cd95..4a1340fe 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problem/Problem.java +++ b/src/main/java/kr/co/morandi/backend/domain/problem/model/Problem.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.problem; +package kr.co.morandi.backend.domain.problem.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; import lombok.*; -import static kr.co.morandi.backend.domain.problem.ProblemStatus.INIT; +import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.INIT; @Entity @Getter diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/ProblemStatus.java b/src/main/java/kr/co/morandi/backend/domain/problem/model/ProblemStatus.java similarity index 92% rename from src/main/java/kr/co/morandi/backend/domain/problem/ProblemStatus.java rename to src/main/java/kr/co/morandi/backend/domain/problem/model/ProblemStatus.java index 3a1ea179..e135f903 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problem/ProblemStatus.java +++ b/src/main/java/kr/co/morandi/backend/domain/problem/model/ProblemStatus.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.problem; +package kr.co.morandi.backend.domain.problem.model; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithm.java b/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/model/ProblemAlgorithm.java similarity index 77% rename from src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithm.java rename to src/main/java/kr/co/morandi/backend/domain/problemalgorithm/model/ProblemAlgorithm.java index 7f41690f..93e6274d 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithm.java +++ b/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/model/ProblemAlgorithm.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.problemalgorithm; +package kr.co.morandi.backend.domain.problemalgorithm.model; import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.algorithm.Algorithm; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.algorithm.model.Algorithm; +import kr.co.morandi.backend.domain.problem.model.Problem; import lombok.*; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/record/Record.java b/src/main/java/kr/co/morandi/backend/domain/record/Record.java index f3351800..8fd957e3 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/Record.java +++ b/src/main/java/kr/co/morandi/backend/domain/record/Record.java @@ -2,10 +2,10 @@ import jakarta.persistence.*; import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -15,6 +15,8 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Entity @Inheritance(strategy = InheritanceType.JOINED) @@ -22,10 +24,10 @@ @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) -public abstract class Record extends BaseEntity { +public abstract class Record extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long detailId; + private Long recordId; private LocalDateTime testDate; @@ -36,16 +38,17 @@ public abstract class Record extends BaseEntity { private Member member; @Builder.Default - @OneToMany(mappedBy = "record", cascade = CascadeType.ALL) - private List details = new ArrayList<>(); - protected abstract Detail createDetail(Member member, Problem problem, - Record record, Defense defense); - protected Record(LocalDateTime testDate, Defense defense, Member member, List problems) { + @OneToMany(mappedBy = "record", cascade = CascadeType.ALL, targetEntity = Detail.class) + private List details = new ArrayList<>(); + + protected abstract T createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense); + + protected Record(LocalDateTime testDate, Defense defense, Member member, Map problems) { this.testDate = testDate; this.defense = defense; this.member = member; - this.details = problems.stream() - .map(problem -> this.createDetail(member, problem, this, defense)) - .toList(); + this.details = problems.entrySet().stream() + .map(problem -> this.createDetail(member, problem.getKey(), problem.getValue(), this, defense)) + .collect(Collectors.toCollection(ArrayList::new)); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomDefenseRecordRepository.java b/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomDefenseRecordRepository.java deleted file mode 100644 index 0bab1c85..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomDefenseRecordRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.record.customdefense; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CustomDefenseRecordRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/customdefenserecord/model/CustomRecord.java similarity index 54% rename from src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecord.java rename to src/main/java/kr/co/morandi/backend/domain/record/customdefenserecord/model/CustomRecord.java index ecca6d25..e34f483c 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecord.java +++ b/src/main/java/kr/co/morandi/backend/domain/record/customdefenserecord/model/CustomRecord.java @@ -1,44 +1,41 @@ -package kr.co.morandi.backend.domain.record.customdefense; +package kr.co.morandi.backend.domain.record.customdefenserecord.model; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.defense.customdefense.CustomDefense; -import kr.co.morandi.backend.domain.detail.customdefense.CustomDetail; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; +import kr.co.morandi.backend.domain.detail.customdefense.model.CustomDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; + import java.time.LocalDateTime; -import java.util.List; +import java.util.Map; @Entity @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @DiscriminatorValue("CustomRecord") -public class CustomRecord extends Record { +public class CustomRecord extends Record { private Long totalSolvedTime; private Integer solvedCount; private Integer problemCount; @Override - public Detail createDetail(Member member, Problem problem, - Record record, Defense defense) { - return CustomDetail.create(member, problem, record, defense); + public CustomDetail createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + return CustomDetail.create(member, sequenceNumber, problem, records, defense); } - private CustomRecord(CustomDefense customDefense, Member member, LocalDateTime testDate, - List problems) { + private CustomRecord(CustomDefense customDefense, Member member, LocalDateTime testDate, Map problems) { super(testDate, customDefense, member, problems); this.totalSolvedTime = 0L; this.solvedCount = 0; this.problemCount = customDefense.getProblemCount(); } - public static CustomRecord create(CustomDefense customDefense, Member member, LocalDateTime testDate, - List problems) { + public static CustomRecord create(CustomDefense customDefense, Member member, LocalDateTime testDate, Map problems) { return new CustomRecord(customDefense, member, testDate, problems); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java deleted file mode 100644 index 9786e6f9..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecord.java +++ /dev/null @@ -1,48 +0,0 @@ -package kr.co.morandi.backend.domain.record.dailydefense; - -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefense; -import kr.co.morandi.backend.domain.detail.dailydefense.DailyDetail; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.record.Record; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.List; - -@Entity -@Getter -@SuperBuilder -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@DiscriminatorValue("DailyDefenseRecord") -public class DailyRecord extends Record { - private Long solvedCount; - private Integer problemCount; - - private DailyRecord(LocalDateTime date, Defense defense, - Member member, List problems) { - super(date, defense, member, problems); - this.solvedCount = 0L; - this.problemCount = problems.size(); - } - @Override - protected Detail createDetail(Member member, Problem problem, Record record, Defense defense) { - return DailyDetail.create(member, problem, record, defense); - } - public static DailyRecord create(LocalDateTime date, DailyDefense DailyDefense, - Member member, List problems) { - - if (Duration.between(DailyDefense.getDate(), date).toDays() >= 1) - throw new IllegalArgumentException("오늘의 문제 기록은 출제 시점으로부터 하루 이내에 생성되어야 합니다."); - - return new DailyRecord(date, DailyDefense, member, problems); - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/record/dailyrecord/model/DailyRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/dailyrecord/model/DailyRecord.java new file mode 100644 index 00000000..70b8e93a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/domain/record/dailyrecord/model/DailyRecord.java @@ -0,0 +1,77 @@ +package kr.co.morandi.backend.domain.record.dailyrecord.model; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.detail.Detail; +import kr.co.morandi.backend.domain.detail.dailydefense.model.DailyDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.Record; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("DailyDefenseRecord") +public class DailyRecord extends Record { + + private Long solvedCount; + private Integer problemCount; + + public boolean isSolvedProblem(Long problemNumber) { + return super.getDetails().stream() + .anyMatch(detail -> detail.getProblemNumber().equals(problemNumber) + && detail.getIsSolved()); + } + @Override + protected DailyDetail createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + return DailyDetail.create(member, sequenceNumber, problem, records, defense); + } + + public static DailyRecord tryDefense(LocalDateTime date, DailyDefense dailyDefense, Member member, Map problems) { + + if (!date.toLocalDate().equals(dailyDefense.getDate())) { + throw new IllegalArgumentException("오늘의 문제 기록은 출제 날짜와 같은 날에 생성되어야 합니다."); + } + + return new DailyRecord(date, dailyDefense, member, problems); + } + + public void tryMoreProblem(Map problem) { + // 이미 시도한 문제들의 problemId를 가져오고 + final Set collect = super.getDetails().stream() + .map(Detail::getProblem) + .map(Problem::getProblemId) + .collect(Collectors.toSet()); + + // 시도하려는 문제들 중 이미 시도한 문제들을 제외한 문제들만 추가 + final List newDetails = problem.entrySet().stream() + .filter(entry -> !collect.contains(entry.getValue().getProblemId())) + .map(p -> createDetail(this.getMember(), p.getKey(), p.getValue(), this, this.getDefense())) + .toList(); + + // 문제 추가 + super.getDetails().addAll(newDetails); + + // 새로운 문제 추가로 문제 수 증가 + this.problemCount += newDetails.size(); + } + private DailyRecord(LocalDateTime date, Defense defense, Member member, Map problems) { + super(date, defense, member, problems); + this.solvedCount = 0L; + this.problemCount = problems.size(); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/randomrecord/model/RandomRecord.java similarity index 59% rename from src/main/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecord.java rename to src/main/java/kr/co/morandi/backend/domain/record/randomrecord/model/RandomRecord.java index 76a29c6e..2da47046 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecord.java +++ b/src/main/java/kr/co/morandi/backend/domain/record/randomrecord/model/RandomRecord.java @@ -1,13 +1,12 @@ -package kr.co.morandi.backend.domain.record.randomdefense; +package kr.co.morandi.backend.domain.record.randomrecord.model; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.random.RandomDefense; -import kr.co.morandi.backend.domain.detail.randomdefense.RandomDetail; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; +import kr.co.morandi.backend.domain.detail.randomdefense.model.RandomDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; @@ -15,34 +14,32 @@ import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; -import java.util.List; +import java.util.Map; @Entity @Getter @SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("RandomDefenseRecord") -public class RandomRecord extends Record { +public class RandomRecord extends Record { private Long totalSolvedTime; private Integer solvedCount; private Integer problemCount; private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; private static final Integer INITIAL_SOLVED_COUNT = 0; - private RandomRecord(LocalDateTime testDate, RandomDefense randomDefense, Member member, - List problems) { + + private RandomRecord(LocalDateTime testDate, RandomDefense randomDefense, Member member, Map problems) { super(testDate, randomDefense, member, problems); this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; this.solvedCount = INITIAL_SOLVED_COUNT; this.problemCount = problems.size(); } @Override - protected Detail createDetail(Member member, Problem problem, Record record, Defense defense) { - return RandomDetail.create(member, problem, record, defense); + protected RandomDetail createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + return RandomDetail.create(member, sequenceNumber, problem, records, defense); } - public static RandomRecord create(RandomDefense randomDefense, Member member, LocalDateTime testDate, - List problems) { - + public static RandomRecord create(RandomDefense randomDefense, Member member, LocalDateTime testDate, Map problems) { return new RandomRecord(testDate, randomDefense, member, problems); } } diff --git a/src/main/java/kr/co/morandi/backend/domain/record/stagedefense/StageRecord.java b/src/main/java/kr/co/morandi/backend/domain/record/stagerecord/model/StageRecord.java similarity index 55% rename from src/main/java/kr/co/morandi/backend/domain/record/stagedefense/StageRecord.java rename to src/main/java/kr/co/morandi/backend/domain/record/stagerecord/model/StageRecord.java index 6a84a54e..13984ebf 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/stagedefense/StageRecord.java +++ b/src/main/java/kr/co/morandi/backend/domain/record/stagerecord/model/StageRecord.java @@ -1,12 +1,11 @@ -package kr.co.morandi.backend.domain.record.stagedefense; +package kr.co.morandi.backend.domain.record.stagerecord.model; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.detail.stagedefense.StageDetail; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.detail.stagedefense.model.StageDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import kr.co.morandi.backend.domain.record.Record; import lombok.AccessLevel; import lombok.Getter; @@ -14,33 +13,31 @@ import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; -import java.util.List; +import java.util.Map; @Entity @SuperBuilder @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("StageDefenseRecord") -public class StageRecord extends Record { +public class StageRecord extends Record { private Long totalSolvedTime; private Long stageCount; private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; private static final Long INITIAL_STAGE_NUMBER = 1L; private static final Long INITIAL_STAGE_COUNT = 1L; - private StageRecord(Defense defense, LocalDateTime testDate, - Member member, List problems) { + + private StageRecord(Defense defense, LocalDateTime testDate, Member member, Map problems) { super(testDate, defense, member, problems); this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; this.stageCount = INITIAL_STAGE_COUNT; } @Override - protected Detail createDetail(Member member, Problem problem, - Record record, Defense defense) { - return StageDetail.create(INITIAL_STAGE_NUMBER, member, problem, record, defense); + protected StageDetail createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + return StageDetail.create(member, INITIAL_STAGE_NUMBER, problem, records, defense); } - public static StageRecord create(Defense defense, LocalDateTime testDate, - Member member, Problem problem) { - return new StageRecord(defense, testDate, member, List.of(problem)); + public static StageRecord create(Defense defense, LocalDateTime testDate, Member member, Problem problem) { + return new StageRecord(defense, testDate, member, Map.of(INITIAL_STAGE_NUMBER, problem)); } } diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseAdapter.java b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseAdapter.java new file mode 100644 index 00000000..4aa5b4e7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseAdapter.java @@ -0,0 +1,29 @@ +package kr.co.morandi.backend.infrastructure.adapter.defense.defense.dailydefense; + +import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; + +@Service +@RequiredArgsConstructor +public class DailyDefenseAdapter implements DailyDefensePort { + + private final DailyDefenseRepository dailyDefenseRepository; + + @Override + public DailyDefense findDailyDefense(DefenseType defenseType, LocalDate date) { + return dailyDefenseRepository.findDailyDefenseByTypeAndDate(defenseType, date) + .orElseThrow(() -> new IllegalArgumentException("DailyDefense가 존재하지 않습니다")); + } + + @Override + public DailyDefense saveDailyDefense(DailyDefense dailyDefense) { + return dailyDefenseRepository.save(dailyDefense); + } + +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapter.java b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapter.java new file mode 100644 index 00000000..07deeea7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapter.java @@ -0,0 +1,46 @@ +package kr.co.morandi.backend.infrastructure.adapter.defense.defense.dailydefense; + +import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefenseProblemPort; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class DailyDefenseProblemAdapter implements DailyDefenseProblemPort { + + private final ProblemRepository problemRepository; + + @Override + public Map getDailyDefenseProblem(Map criteria) { + + Pageable pageable = PageRequest.of(0, 1); + + return criteria.entrySet().stream() + .map(entry -> { + final RandomCriteria randomCriteria = entry.getValue(); + final RandomCriteria.DifficultyRange difficultyRange = randomCriteria.getDifficultyRange(); + final ProblemTier startTier = difficultyRange.getStartDifficulty(); + final ProblemTier endTier = difficultyRange.getEndDifficulty(); + + final List dailyDefenseProblems = + problemRepository.getDailyDefenseProblems(ProblemTier.tierRangeOf(startTier, endTier), + randomCriteria.getMinSolvedCount(), + randomCriteria.getMaxSolvedCount(), + pageable); + + return Map.entry(entry.getKey(), dailyDefenseProblems.get(0)); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } +} + diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapter.java b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapter.java new file mode 100644 index 00000000..daa71dae --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapter.java @@ -0,0 +1,32 @@ +package kr.co.morandi.backend.infrastructure.adapter.defense.record.dailyrecord; + +import kr.co.morandi.backend.application.port.out.record.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord.DailyRecordRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class DailyRecordAdapter implements DailyRecordPort { + + private final DailyRecordRepository dailyRecordRepository; + + @Override + public DailyRecord saveDailyRecord(DailyRecord dailyRecord) { + return dailyRecordRepository.save(dailyRecord); + } + @Override + public Optional findDailyRecord(Member member, LocalDate date) { + return dailyRecordRepository.findDailyRecordByMemberAndDate(member, date); + + } + @Override + public Optional findDailyRecord(Member member, Long recordId, LocalDate date) { + return dailyRecordRepository.findDailyRecordByRecordId(member, recordId, date); + } +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapter.java b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapter.java new file mode 100644 index 00000000..d9a2fc91 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapter.java @@ -0,0 +1,30 @@ +package kr.co.morandi.backend.infrastructure.adapter.defensesession; + +import kr.co.morandi.backend.application.port.out.defensemanagement.defensesession.DefenseSessionPort; +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; + +@Service +@RequiredArgsConstructor +public class DefenseSessionAdapter implements DefenseSessionPort { + + private final DefenseSessionRepository defenseSessionRepository; + + @Override + public DefenseSession saveDefenseSession(DefenseSession defenseSession) { + return defenseSessionRepository.save(defenseSession); + } + + @Override + public Optional findTodaysDailyDefenseSession(Member member, LocalDateTime now) { + return defenseSessionRepository.findDailyDefenseSession(member, DAILY, now); + } +} diff --git a/src/main/java/kr/co/morandi/backend/config/AlgorithmInitializer.java b/src/main/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializer.java similarity index 88% rename from src/main/java/kr/co/morandi/backend/config/AlgorithmInitializer.java rename to src/main/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializer.java index 66d0b9a0..09cb4f4d 100644 --- a/src/main/java/kr/co/morandi/backend/config/AlgorithmInitializer.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializer.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.config; +package kr.co.morandi.backend.infrastructure.config; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -6,8 +6,8 @@ import org.springframework.core.io.Resource; import org.springframework.beans.factory.annotation.Value; -import kr.co.morandi.backend.domain.algorithm.Algorithm; -import kr.co.morandi.backend.domain.algorithm.AlgorithmRepository; +import kr.co.morandi.backend.domain.algorithm.model.Algorithm; +import kr.co.morandi.backend.infrastructure.persistence.algorithm.AlgorithmRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/kr/co/morandi/backend/config/JpaAuditingConfig.java b/src/main/java/kr/co/morandi/backend/infrastructure/config/JpaAuditingConfig.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/config/JpaAuditingConfig.java rename to src/main/java/kr/co/morandi/backend/infrastructure/config/JpaAuditingConfig.java index 32bccb4c..e106c29a 100644 --- a/src/main/java/kr/co/morandi/backend/config/JpaAuditingConfig.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/config/JpaAuditingConfig.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.config; +package kr.co.morandi.backend.infrastructure.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/config/SchedulingConfig.java b/src/main/java/kr/co/morandi/backend/infrastructure/config/SchedulingConfig.java new file mode 100644 index 00000000..baf05f14 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/config/SchedulingConfig.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend.infrastructure.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SchedulingConfig { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepository.java similarity index 63% rename from src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepository.java index e72cad9e..a29c496f 100644 --- a/src/main/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepository.java @@ -1,5 +1,6 @@ -package kr.co.morandi.backend.domain.algorithm; +package kr.co.morandi.backend.infrastructure.persistence.algorithm; +import kr.co.morandi.backend.domain.algorithm.model.Algorithm; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMarkRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/bookmark/BookMarkRepository.java similarity index 52% rename from src/main/java/kr/co/morandi/backend/domain/bookmark/BookMarkRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/bookmark/BookMarkRepository.java index 5bc17acd..3993d02e 100644 --- a/src/main/java/kr/co/morandi/backend/domain/bookmark/BookMarkRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/bookmark/BookMarkRepository.java @@ -1,5 +1,6 @@ -package kr.co.morandi.backend.domain.bookmark; +package kr.co.morandi.backend.infrastructure.persistence.bookmark; +import kr.co.morandi.backend.domain.bookmark.model.BookMark; import org.springframework.data.jpa.repository.JpaRepository; public interface BookMarkRepository extends JpaRepository { diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java new file mode 100644 index 00000000..95b00313 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.infrastructure.persistence.defense; + +import kr.co.morandi.backend.domain.defense.Defense; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseProblemRepository.java similarity index 50% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblemRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseProblemRepository.java index e2ed7a84..b9230b6e 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseProblemRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseProblemRepository.java @@ -1,5 +1,6 @@ -package kr.co.morandi.backend.domain.defense.customdefense; +package kr.co.morandi.backend.infrastructure.persistence.defense.customdefense; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefenseProblem; import org.springframework.data.jpa.repository.JpaRepository; public interface CustomDefenseProblemRepository extends JpaRepository { diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseRepository.java similarity index 50% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseRepository.java index 9981593c..89ce599e 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseRepository.java @@ -1,5 +1,7 @@ -package kr.co.morandi.backend.domain.defense.customdefense; +package kr.co.morandi.backend.infrastructure.persistence.defense.customdefense; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; +import kr.co.morandi.backend.domain.defense.customdefense.model.Visibility; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepository.java new file mode 100644 index 00000000..25426831 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepository.java @@ -0,0 +1,17 @@ +package kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface DailyDefenseProblemRepository extends JpaRepository { + @Query(""" + select ddp + from DailyDefenseProblem as ddp + left join fetch ddp.problem p + where ddp.dailyDefense.defenseId = :defenseId + """) + List findAllProblemsContainsDefenseId(Long defenseId); +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseRepository.java new file mode 100644 index 00000000..390159e3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseRepository.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense; + +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.time.LocalDate; +import java.util.Optional; + +public interface DailyDefenseRepository extends JpaRepository { + + @Query(""" + SELECT dd + from DailyDefense dd + left join fetch dd.dailyDefenseProblems ddp + where dd.defenseType = :defenseType + and dd.date = :date + """) + Optional findDailyDefenseByTypeAndDate(DefenseType defenseType, LocalDate date); + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/random/RandomDefenseRepository.java similarity index 51% rename from src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefenseRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/random/RandomDefenseRepository.java index 53014441..43ff6314 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/random/RandomDefenseRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/random/RandomDefenseRepository.java @@ -1,5 +1,6 @@ -package kr.co.morandi.backend.domain.defense.random; +package kr.co.morandi.backend.infrastructure.persistence.defense.random; +import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; import org.springframework.data.jpa.repository.JpaRepository; public interface RandomDefenseRepository extends JpaRepository { diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java new file mode 100644 index 00000000..ffc59b5a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.infrastructure.persistence.defense.stagedefense; + +import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StageDefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/session/DefenseSessionRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/session/DefenseSessionRepository.java new file mode 100644 index 00000000..f247efd9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/session/DefenseSessionRepository.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session; + +import kr.co.morandi.backend.domain.defense.DefenseType; +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.member.model.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.time.LocalDateTime; +import java.util.Optional; + +public interface DefenseSessionRepository extends JpaRepository { + @Query(""" + select ds + from DefenseSession as ds + left join fetch ds.sessionDetails + where ds.endDateTime > :now + and ds.defenseType = :defenseType + and ds.member = :member + """) + Optional findDailyDefenseSession(Member member, DefenseType defenseType, LocalDateTime now); +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java new file mode 100644 index 00000000..9dac9ee3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.infrastructure.persistence.defensemanagement.sessiondetail; + +import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SessionDetailRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java new file mode 100644 index 00000000..7f6ae6a8 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.infrastructure.persistence.defensemanagement.tempcode; + +import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TempCodeRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikesRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensememberlikes/ContentMemberLikesRepository.java similarity index 50% rename from src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikesRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensememberlikes/ContentMemberLikesRepository.java index 377decc3..ae52e635 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/ContentMemberLikesRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensememberlikes/ContentMemberLikesRepository.java @@ -1,5 +1,6 @@ -package kr.co.morandi.backend.domain.contentmemberlikes; +package kr.co.morandi.backend.infrastructure.persistence.defensememberlikes; +import kr.co.morandi.backend.domain.contentmemberlikes.model.ContentMemberLikes; import org.springframework.data.jpa.repository.JpaRepository; public interface ContentMemberLikesRepository extends JpaRepository { diff --git a/src/main/java/kr/co/morandi/backend/domain/member/MemberRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepository.java similarity index 60% rename from src/main/java/kr/co/morandi/backend/domain/member/MemberRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepository.java index 97e476e9..931bf849 100644 --- a/src/main/java/kr/co/morandi/backend/domain/member/MemberRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepository.java @@ -1,5 +1,6 @@ -package kr.co.morandi.backend.domain.member; +package kr.co.morandi.backend.infrastructure.persistence.member; +import kr.co.morandi.backend.domain.member.model.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepository.java new file mode 100644 index 00000000..d0ca5c7a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepository.java @@ -0,0 +1,29 @@ +package kr.co.morandi.backend.infrastructure.persistence.problem; + +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.problem.model.ProblemStatus; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface ProblemRepository extends JpaRepository { + + @Query(""" + SELECT p + FROM Problem p + WHERE p.problemStatus = 'ACTIVE' + AND p.problemTier IN :problemTiers + AND p.solvedCount >= :startSolvedCount + AND p.solvedCount <= :endSolvedCount + AND p NOT IN (SELECT ddp.problem + FROM DailyDefenseProblem ddp) + ORDER BY FUNCTION('RAND') + """) + List getDailyDefenseProblems(List problemTiers, Long startSolvedCount, Long endSolvedCount, Pageable pageable); + + List findAllByProblemStatus(ProblemStatus problemStatus); +} + diff --git a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problemalgorithm/ProblemAlgorithmRepository.java similarity index 50% rename from src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithmRepository.java rename to src/main/java/kr/co/morandi/backend/infrastructure/persistence/problemalgorithm/ProblemAlgorithmRepository.java index eb9f54b1..dcd57750 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/ProblemAlgorithmRepository.java +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problemalgorithm/ProblemAlgorithmRepository.java @@ -1,5 +1,6 @@ -package kr.co.morandi.backend.domain.problemalgorithm; +package kr.co.morandi.backend.infrastructure.persistence.problemalgorithm; +import kr.co.morandi.backend.domain.problemalgorithm.model.ProblemAlgorithm; import org.springframework.data.jpa.repository.JpaRepository; public interface ProblemAlgorithmRepository extends JpaRepository { diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java new file mode 100644 index 00000000..6eb10b13 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.infrastructure.persistence.record.customrecord; + +import kr.co.morandi.backend.domain.record.customdefenserecord.model.CustomRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDefenseRecordRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepository.java new file mode 100644 index 00000000..ca9dc652 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepository.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord; + +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.time.LocalDate; +import java.util.Optional; + +public interface DailyRecordRepository extends JpaRepository { + + @Query(""" + select dr + from DailyRecord dr + left join fetch dr.details d + left join fetch d.problem + where dr.member = :member + and CAST(dr.testDate as localdate) = :date + """) + Optional findDailyRecordByMemberAndDate(Member member, LocalDate date); + + @Query(""" + select dr + from DailyRecord dr + left join fetch dr.details d + left join fetch d.problem + where dr.member = :member + and dr.recordId = :recordId + and CAST(dr.testDate as localdate) = :date + """) + Optional findDailyRecordByRecordId(Member member, Long recordId, LocalDate date); + +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java b/src/main/java/kr/co/morandi/backend/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java new file mode 100644 index 00000000..79ac3641 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java @@ -0,0 +1,29 @@ +package kr.co.morandi.backend.infrastructure.scheduler.dailydefense; + +import kr.co.morandi.backend.domain.defense.dailydefense.service.DailyDefenseGenerationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Component +@Slf4j +@RequiredArgsConstructor +public class DailyDefenseGenerationScheduler { + + private final DailyDefenseGenerationService dailyDefenseGenerationService; + + @Scheduled(cron = "0 0 23 * * ?", zone = "Asia/Seoul") + public void generateDailyDefense() { + LocalDateTime now = LocalDateTime.now(); + boolean result = dailyDefenseGenerationService.generateDailyDefense(now); + if (!result) { + throw new RuntimeException("Daily defense generation failed"); + } + + log.info("Daily defense generation success"); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/web/defensemanagement/dailydefense/dto/request/StartDailyDefenseRequest.java b/src/main/java/kr/co/morandi/backend/web/defensemanagement/dailydefense/dto/request/StartDailyDefenseRequest.java new file mode 100644 index 00000000..12c789dd --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/web/defensemanagement/dailydefense/dto/request/StartDailyDefenseRequest.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.web.defensemanagement.dailydefense.dto.request; + +import kr.co.morandi.backend.domain.defensemanagement.management.request.StartDailyDefenseServiceRequest; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class StartDailyDefenseRequest { + + private Long problemNumber; + + @Builder + private StartDailyDefenseRequest(Long problemNumber) { + this.problemNumber = problemNumber; + } + + public StartDailyDefenseServiceRequest toServiceRequest() { + return StartDailyDefenseServiceRequest.builder() + .problemNumber(problemNumber) + .build(); + } +} diff --git a/src/test/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPortTest.java b/src/test/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPortTest.java new file mode 100644 index 00000000..e1d4cc8a --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPortTest.java @@ -0,0 +1,115 @@ +package kr.co.morandi.backend.application.port.out.defensemanagement.defensesession; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; +import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class DefenseSessionPortTest { + + @Autowired + private DefenseSessionPort defenseSessionPort; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyDefenseProblemRepository dailyDefenseProblemRepository; + + @Autowired + private DefenseSessionRepository defenseSessionRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private MemberRepository memberRepository; + + @AfterEach + void tearDown() { + defenseSessionRepository.deleteAll(); + dailyDefenseProblemRepository.deleteAllInBatch(); + dailyDefenseRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + memberRepository.deleteAllInBatch(); + } + + @DisplayName("DailyDefense 세션을 조회할 수 있다.") + @Test + void findDailyDefenseSession() { + // given + final Member member = createMember(); + final DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2021, 10, 1, 0, 0); + + Long recordId = 1L; + final DefenseSession defenseSession = DefenseSession.startSession(member, recordId, DAILY, Set.of(2L), startTime, dailyDefense.getEndTime(startTime)); + defenseSessionPort.saveDefenseSession(defenseSession); + + // when + final Optional maybeDefenseSession = defenseSessionPort.findTodaysDailyDefenseSession(member, startTime.plusMinutes(1)); + + // then + assertThat(maybeDefenseSession).isPresent() + .get() + .extracting(DefenseSession::getRecordId) + .isEqualTo(recordId); + + final DefenseSession session = maybeDefenseSession.get(); + assertThat(session.getSessionDetails()).hasSize(1) + .extracting("problemNumber") + .contains( + 2L + ); + + } + private Map getProblem(DailyDefense dailyDefense, Long problemNumber) { + return dailyDefense.getDailyDefenseProblems().stream() + .filter(problem -> Objects.equals(problem.getProblemNumber(), problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + + private DailyDefense createDailyDefense() { + LocalDate createdDate = LocalDate.of(2021, 10, 1); + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return dailyDefenseRepository.save(DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java index 1600b84f..fa98d26e 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java @@ -1,7 +1,8 @@ package kr.co.morandi.backend.domain.defense.customdefense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -10,14 +11,39 @@ import java.util.Collections; import java.util.List; -import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.result.StatusResultMatchersExtensionsKt.isEqualTo; @ActiveProfiles("test") class CustomDefenseTest { + @DisplayName("커스텀 디펜스를 시작할 떄 끝나는 시간을 계산하면 시작 시간에 제한 시간을 더한 값이다.") + @Test + void getEndTime() { + // given + Member member = Member.create("test1", "test1", GOOGLE, "test1", "test1"); + + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + List problems = List.of(problem1, problem2); + + LocalDateTime now = LocalDateTime.of(2024, 2, 21, 0, 0, 0, 0); + + CustomDefense customDefense = CustomDefense.create(problems, member, "커스텀 디펜스1", "커스텀 디펜스1 설명", OPEN, GOLD, 60L, now); + + + // when + final LocalDateTime endTime = customDefense.getEndTime(now); + + + // then + assertThat(endTime) + .isEqualTo(now.plusMinutes(60L)); + } + @DisplayName("커스텀 디펜스를 생성하면 등록 시간을 기록한다.") @Test void registeredWithDateTime() { diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java index 6be671a1..8b52290b 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java @@ -1,14 +1,19 @@ package kr.co.morandi.backend.domain.defense.dailydefense; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.problem.model.Problem; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") @@ -17,13 +22,17 @@ class DailyDefenseProblemTest { @Test void submitCountIsZero() { // given - List problems = createProblems(); - LocalDateTime now = LocalDateTime.now(); - DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); - List DailyDefenseProblemsList = dailyDefense.getDailyDefenseProblems(); + LocalDateTime now = LocalDateTime.of(2021, 1, 1, 0, 0, 0); - // when & then - assertThat(DailyDefenseProblemsList) + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + + // when + DailyDefense dailyDefense = DailyDefense.create(now.toLocalDate(), "오늘의 문제 테스트", problemMap); + + // then + assertThat(dailyDefense.getDailyDefenseProblems()) .extracting("submitCount") .containsExactlyInAnyOrder(0L, 0L, 0L); } @@ -32,13 +41,17 @@ void submitCountIsZero() { @Test void solvedCountIsZero() { // given - List problems = createProblems(); - LocalDateTime now = LocalDateTime.now(); - DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); - List DailyDefenseProblemsList = dailyDefense.getDailyDefenseProblems(); + LocalDateTime now = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + + // when + DailyDefense dailyDefense = DailyDefense.create(now.toLocalDate(), "오늘의 문제 테스트", problemMap); - // when & then - assertThat(DailyDefenseProblemsList) + // then + assertThat(dailyDefense.getDailyDefenseProblems()) .extracting("solvedCount") .containsExactlyInAnyOrder(0L, 0L, 0L); } diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java index 2c8f1983..c4a72928 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java @@ -1,27 +1,87 @@ package kr.co.morandi.backend.domain.defense.dailydefense; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; +import kr.co.morandi.backend.domain.problem.model.Problem; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ActiveProfiles("test") class DailyDefenseTest { + + @DisplayName("오늘의 문제세트에 포함된 문제를 가져올 수 있다.") + @Test + void getTryingProblem() { + // given + LocalDateTime now = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + final DailyDefense dailyDefense = DailyDefense.create(now.toLocalDate(), "오늘의 문제 테스트", problemMap); + + Map expectedProblems = Map.of( + 1L, problemMap.get(1L), + 2L, problemMap.get(2L), + 3L, problemMap.get(3L) + ); + final ProblemGenerationService problemGenerationService = mock(ProblemGenerationService.class); + + when(problemGenerationService.getDefenseProblems(dailyDefense)).thenReturn(expectedProblems); + + // when + final Map tryingProblem = dailyDefense.getTryingProblem(1L, problemGenerationService); + + // then + assertThat(tryingProblem.entrySet()).isNotEmpty() + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .containsExactlyInAnyOrder( + tuple(1L, problemMap.get(1L)) + ); + + } + @DisplayName("오늘의 문제를 응시할 때 끝나는 시간은 오늘의 문제 날짜 직전까지이다.") + @Test + void getEndTime() { + // given + LocalDateTime now = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + + // when + DailyDefense dailyDefense = DailyDefense.create(now.toLocalDate(), "오늘의 문제 테스트", problemMap); + + // then + assertThat(dailyDefense.getEndTime(now)) + .isEqualTo(now.toLocalDate().atTime(23, 59, 59)); + } @DisplayName("오늘의 문제 세트가 만들어진 시점에서 시도한 사람의 수는 0명 이어야 한다.") @Test void attemptCountIsZero() { // given - List problems = createProblems(); - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); // when - DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); + DailyDefense dailyDefense = DailyDefense.create(now.toLocalDate(), "오늘의 문제 테스트", problemMap); // then assertThat(dailyDefense.getAttemptCount()).isZero(); @@ -30,11 +90,14 @@ void attemptCountIsZero() { @Test void testDateEqualNow() { // given - List problems = createProblems(); - LocalDateTime now = LocalDateTime.now(); + LocalDate now = LocalDate.of(2021, 1, 1); + + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); // when - DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); + DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problemMap); // then assertThat(dailyDefense.getDate()).isEqualTo(now); @@ -43,11 +106,14 @@ void testDateEqualNow() { @Test void contentNameIsEqual() { // given - List problems = createProblems(); - LocalDateTime now = LocalDateTime.now(); + LocalDateTime now = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); // when - DailyDefense dailyDefense = DailyDefense.create(now, "오늘의 문제 테스트", problems); + DailyDefense dailyDefense = DailyDefense.create(now.toLocalDate(), "오늘의 문제 테스트", problemMap); // then assertThat(dailyDefense.getContentName()).isEqualTo("오늘의 문제 테스트"); diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationServiceTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationServiceTest.java new file mode 100644 index 00000000..d7e88561 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationServiceTest.java @@ -0,0 +1,86 @@ +package kr.co.morandi.backend.domain.defense.dailydefense.service; + +import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class DailyDefenseGenerationServiceTest { + + @Autowired + private DailyDefenseGenerationService dailyDefenseGenerationService; + + @Autowired + private DailyDefensePort dailyDefensePort; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyDefenseProblemRepository dailyDefenseProblemRepository; + + @Autowired + private ProblemRepository problemRepository; + + @AfterEach + void tearDown() { + dailyDefenseProblemRepository.deleteAllInBatch(); + dailyDefenseRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + } + + @DisplayName("오늘의 문제를 생성할 수 있다.") + @Test + void generateDailyDefense() { + // given + final List problems = createProblems(); + LocalDateTime requestTIme = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + // when + final boolean result = dailyDefenseGenerationService.generateDailyDefense(requestTIme); + + // then + assertThat(result).isTrue(); + + final DailyDefense nextDaysDailyDefense = dailyDefensePort.findDailyDefense(DAILY, requestTIme.plusDays(1L).toLocalDate()); + + assertThat(nextDaysDailyDefense).isNotNull(); + assertThat(nextDaysDailyDefense.getDailyDefenseProblems()).hasSize(5); + } + + private List createProblems() { + Problem problem1 = Problem.create(1L, B3, 1500L); + Problem problem2 = Problem.create(2L, S4, 1500L); + Problem problem3 = Problem.create(3L, S2, 1500L); + Problem problem4 = Problem.create(4L, G4, 1500L); + Problem problem5 = Problem.create(5L, G2, 1500L); + Problem problem6 = Problem.create(6L, B3, 1500L); + Problem problem7 = Problem.create(7L, S4, 1500L); + Problem problem8 = Problem.create(8L, S2, 1500L); + Problem problem9 = Problem.create(9L, G4, 1500L); + Problem problem10 = Problem.create(10L, G2, 1500L); + + final List problemList = List.of(problem1, problem2, problem3, problem4, problem5, problem6, problem7, problem8, problem9, problem10); + problemList.forEach(Problem::activate); + return problemRepository.saveAll(problemList); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java index 401981df..ea1051df 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java @@ -1,13 +1,13 @@ package kr.co.morandi.backend.domain.defense.randomcriteria; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java index 9c0f00ac..83a2bde6 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java @@ -1,7 +1,7 @@ package kr.co.morandi.backend.domain.defense.randomcriteria; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.tier.ProblemTier; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java index b2baa583..e24a8c90 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java @@ -1,18 +1,37 @@ package kr.co.morandi.backend.domain.defense.randomdefense; -import kr.co.morandi.backend.domain.defense.random.RandomDefense; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; +import java.time.LocalDateTime; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @ActiveProfiles("test") class RandomDefenseTest { + @DisplayName("랜덤 디펜스 응시 시 끝나는 시간을 계산하면 시작 시간에 제한 시간을 더한 시간이어야 한다.") + @Test + void getEndTime() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + RandomDefense randomDefense = RandomDefense.create(randomCriteria, 4, 120L, "브론즈 랜덤 디펜스"); + LocalDateTime startTime = LocalDateTime.of(2021, 1, 1, 0, 0); + + // when + final LocalDateTime endTime = randomDefense.getEndTime(startTime); + + // then + assertThat(endTime) + .isEqualTo(startTime.plusMinutes(120L)); + } + @DisplayName("랜덤 디펜스를 생성할 때 등록한 정보가 올바르게 저장되어야 한다.") @Test void createRandomDefense() { diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/service/ProblemGenerationServiceTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/service/ProblemGenerationServiceTest.java new file mode 100644 index 00000000..6868e7df --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/defense/service/ProblemGenerationServiceTest.java @@ -0,0 +1,86 @@ +package kr.co.morandi.backend.domain.defense.service; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class ProblemGenerationServiceTest { + + @Autowired + private ProblemGenerationService problemGenerationService; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyDefenseProblemRepository dailyDefenseProblemRepository; + + @Autowired + private ProblemRepository problemRepository; + + @AfterEach + void tearDown() { + dailyDefenseProblemRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + dailyDefenseRepository.deleteAllInBatch(); + } + + @DisplayName("DailyDefense의 Problem목록을 가져올 수 있다.") + @Test + void getDefenseProblems() { + // given + LocalDate defenseDate = LocalDate.of(2021, 1, 1); + DailyDefense dailyDefense = createDailyDefense(defenseDate); + + // when + Map defenseProblems = problemGenerationService.getDefenseProblems(dailyDefense); + + // then + assertThat(defenseProblems.values()).hasSize(3) + .extracting(Problem::getBaekjoonProblemId, Problem::getProblemTier, Problem::getSolvedCount) + .containsExactlyInAnyOrder( + tuple(1L, B5, 0L), + tuple(2L, S5, 0L), + tuple(3L, G5, 0L) + ); + + } + + private DailyDefense createDailyDefense(LocalDate date) { + + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + + return dailyDefenseRepository.save(DailyDefense.create(date, "오늘의 문제 테스트", problemMap)); + } + + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java b/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java index eefc20d3..05a7621e 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java @@ -1,18 +1,38 @@ package kr.co.morandi.backend.domain.defense.stagedefense; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.stagedefense.StageDefense; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; +import java.time.LocalDateTime; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @ActiveProfiles("test") class StageDefenseTest { + @DisplayName("스테이지 모드를 시작할 때 끝나는 시간은 시작 시간에 제한 시간을 더한 값이어야 한다.") + @Test + void getEndTime() { + // given + RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); + RandomCriteria randomCriteria = RandomCriteria.of(bronzeRange, 100L, 200L); + StageDefense randomStageDefense = StageDefense.create(randomCriteria, 120L, "브론즈 스테이지 모드"); + LocalDateTime startTime = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + + // when + final LocalDateTime endTime = randomStageDefense.getEndTime(startTime); + + // then + assertThat(endTime) + .isEqualTo(startTime.plusMinutes(120L)); + + } + @DisplayName("스테이지 모드를 처음 만들 때 정보가 올바르게 저장되어야 한다.") @Test void createRamdomStageDefense() { diff --git a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementServiceTest.java b/src/test/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementServiceTest.java new file mode 100644 index 00000000..950bc9b3 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementServiceTest.java @@ -0,0 +1,241 @@ +package kr.co.morandi.backend.domain.defensemanagement.management.service; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defensemanagement.management.request.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.domain.defensemanagement.management.response.StartDailyDefenseServiceResponse; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; +import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.sessiondetail.SessionDetailRepository; +import kr.co.morandi.backend.infrastructure.persistence.detail.dailydetail.DailyDetailRepository; +import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord.DailyRecordRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.junit.jupiter.api.Assertions.assertAll; + + +@SpringBootTest +@ActiveProfiles("test") +class DailyDefenseManagementServiceTest { + + @Autowired + private DailyDefenseManagementService dailyDefenseManagementService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyDefenseProblemRepository dailyDefenseProblemRepository; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @Autowired + private DailyDetailRepository dailyDetailRepository; + + @Autowired + private DefenseSessionRepository defenseSessionRepository; + + @Autowired + private SessionDetailRepository sessionDetailRepository; + + @AfterEach + void tearDown() { + sessionDetailRepository.deleteAll(); + defenseSessionRepository.deleteAllInBatch(); + dailyDetailRepository.deleteAllInBatch(); + dailyRecordRepository.deleteAllInBatch(); + dailyDefenseProblemRepository.deleteAllInBatch(); + dailyDefenseRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + memberRepository.deleteAllInBatch(); + } + + @DisplayName("전날 시작했던 시험이 안 끝났더라도 오늘의 문제를 시도하면 해당하는 날짜의 문제를 제공한다.") + @Test + void retryDailyDefenseWhenDayPassed() { + // given + Member member = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "10월 1일 오늘의 문제 테스트"); + createDailyDefense(LocalDate.of(2021, 10, 2), "10월 2일 오늘의 문제 테스트"); + + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() + .problemNumber(1L) + .build(); + dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + + StartDailyDefenseServiceRequest retryRequest = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + + LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 2, 12, 0, 0); + + // when + final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member, retryRequestTime); + + + // then + assertAll( + () -> assertThat(response).isNotNull() + .extracting("lastAccessTime", "contentName", "defenseType") + .contains(retryRequestTime, "10월 2일 오늘의 문제 테스트", DAILY), + + () -> assertThat(response.getDefenseProblems()).hasSize(1) + .extracting("problemNumber", "baekjoonProblemId", "isCorrect") + .contains( + tuple(2L, 2000L, false) + ) + ); + + } + + @DisplayName("시도하던 오늘의 문제 외 다른 문제를 시도하면 새로 시도한 문제를 제공한다.") + @Test + void retryDailyDefenseWithOtherProblem() { + // given + Member member = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() + .problemNumber(1L) + .build(); + dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + + StartDailyDefenseServiceRequest retryRequest = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + + LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); + // when + final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member, retryRequestTime); + + // then + assertAll( + () -> assertThat(response).isNotNull() + .extracting("lastAccessTime") + .isEqualTo(retryRequestTime), + + () -> assertThat(response.getDefenseProblems()).hasSize(1) + .extracting("problemNumber", "baekjoonProblemId", "isCorrect") + .contains( + tuple(2L, 2000L, false) + ) + ); + + } + + @DisplayName("시도하던 오늘의 문제에 대해 다시 시도하면 기존의 문제를 다시 제공한다. (다른 문제 시도나, tempCode 등의 기록이 없으면 기존 시간 그대로 기록돼있음") + @Test + void retryDailyDefense() { + // given + Member member = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + + + LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); + // when + final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(request, member, retryRequestTime); + + // then + assertAll( + () -> assertThat(response).isNotNull() + .extracting("lastAccessTime") + .isEqualTo(requestTime), + + () -> assertThat(response.getDefenseProblems()).hasSize(1) + .extracting("problemNumber", "baekjoonProblemId", "isCorrect") + .contains( + tuple(2L, 2000L, false) + ) + ); + } + + @DisplayName("오늘의 문제를 시작할 수 있다.") + @Test + void startDailyDefense() { + // given + Member member = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + + // when + final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + + // then + + assertAll( + () -> assertThat(response).isNotNull() + .extracting("lastAccessTime") + .isEqualTo(requestTime), + + () -> assertThat(response.getDefenseProblems()).hasSize(1) + .extracting("problemNumber", "baekjoonProblemId", "isCorrect") + .contains( + tuple(2L, 2000L, false) + ) + ); + + } + + private DailyDefense createDailyDefense(LocalDate createdDate, String contentName) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return dailyDefenseRepository.save(DailyDefense.create(createdDate, contentName, problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.create(1000L, B5, 0L); + Problem problem2 = Problem.create(2000L, S5, 0L); + Problem problem3 = Problem.create(3000L, G5, 0L); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + } + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSessionTest.java b/src/test/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSessionTest.java new file mode 100644 index 00000000..a3a22d16 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSessionTest.java @@ -0,0 +1,205 @@ +package kr.co.morandi.backend.domain.defensemanagement.session.model; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; +import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language.CPP; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ActiveProfiles("test") +class DefenseSessionTest { + + @DisplayName("문제 번호를 가지고 있는지 확인한다.") + @Test + void hasTriedProblem() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + Map problems = getProblems(dailyDefense, 1L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + // when + final boolean result = defenseSession.hasTriedProblem(1L); + + // then + assertThat(result).isTrue(); + + } + + @DisplayName("문제 번호를 가지고 있지 않으면 false를 반환한다.") + @Test + void hasNotTriedProblem() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + Map problems = getProblems(dailyDefense, 1L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + // when + final boolean result = defenseSession.hasTriedProblem(2L); + + // then + assertThat(result).isFalse(); + + } + + @DisplayName("Session을 생성하면 TempCode까지 생성된다.") + @Test + void sessionWithTempCode() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + + Map problems = getProblems(dailyDefense, 1L); + + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + // when + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + + // then + assertThat(defenseSession).isNotNull(); + assertThat(defenseSession.getSessionDetails()).hasSize(1) + .extracting("problemNumber") + .containsExactly(1L); + + SessionDetail sessionDetail = defenseSession.getSessionDetails().iterator().next(); + + assertThat(sessionDetail.getTempCodes()) + .isNotEmpty(); + + TempCode tempCode = sessionDetail.getTempCodes().iterator().next(); + assertThat(tempCode.getLanguage()) + .isEqualTo(CPP); + } + + @DisplayName("문제를 추가하면 Detail이 추가된다.") + @Test + void tryMoreProblem() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + + Map problems = getProblems(dailyDefense, 2L); + + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + // when + defenseSession.tryMoreProblem(3L, startTime.plusMinutes(1)); + + // then + assertThat(defenseSession.getSessionDetails()).hasSize(2) + .extracting("problemNumber") + .containsExactlyInAnyOrder(2L, 3L); + + } + + @DisplayName("Record에 따라 시험 세션을 시작하면 마지막 접근 문제 번호가 가장 첫 번째 숫자로 저장된다.") + @Test + void startSessionWithRecord() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + + Map problems = getProblems(dailyDefense, 2L); + + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + // when + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + // then + assertThat(defenseSession.getLastAccessProblemNumber()).isEqualTo(2L); + } + + @DisplayName("문제 번호 없이 Session을 시작하려 하면 예외를 발생한다.") + @Test + void startSessionWithoutProblemNumber() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + + Map problems = getProblems(dailyDefense, null); + + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + // when & then + assertThatThrownBy(() -> DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), new HashSet<>(), startTime, dailyDefense.getEndTime(startTime))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("문제 번호가 없습니다."); + + } + + @DisplayName("Record에 따라 시험 세션을 시작할 수 있다.") + @Test + void startSession() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + + Map problems = getProblems(dailyDefense, 2L); + + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + // when + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + // then + assertThat(defenseSession).isNotNull(); + } + + private Member createMember() { + return Member.create("nickname", "email", GOOGLE, "imageURL", "description"); + } + private DailyDefense createDailyDefense(LocalDate createdDate) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return List.of(problem1, problem2, problem3); + } + private Map getProblems(DailyDefense DailyDefense, Long problemNumber) { + return DailyDefense.getDailyDefenseProblems().stream() + .filter(p -> p.getProblemNumber().equals(problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetailTest.java b/src/test/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetailTest.java new file mode 100644 index 00000000..f87276f2 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetailTest.java @@ -0,0 +1,132 @@ +package kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model; + +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language.CPP; +import static kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language.JAVA; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.Mockito.mock; + +@ActiveProfiles("test") +class SessionDetailTest { + @DisplayName("처음 SessionDetail을 생성하면 tempCode 언어는 CPP로 생성된다.") + @Test + void createSessionDetailWithInitialLanguageType() { + // given + DefenseSession defenseSession = mock(DefenseSession.class); + + // when + final SessionDetail sessionDetail = SessionDetail.create(defenseSession, 1L); + + // then + assertThat(sessionDetail.getTempCodes()) + .hasSize(1) + .extracting("language", "sessionDetail") + .contains( + tuple(CPP, sessionDetail) + ); + + } + @DisplayName("SessionDetail에 저장된 언어로 tempCode를 조회할 수 있다.") + @Test + void getTempCode() { + // given + DefenseSession defenseSession = mock(DefenseSession.class); + final SessionDetail sessionDetail = SessionDetail.create(defenseSession, 1L); + + // when + final TempCode tempCode = sessionDetail.getTempCode(CPP); + + // then + assertThat(tempCode) + .extracting("language","sessionDetail") + .contains(CPP, sessionDetail); + } + + @DisplayName("SessionDetail에 저장되지 않은 언어로 tempCode를 조회하면 새로 생성된다.") + @Test + void getTempCodeWhenNotExists() { + // given + DefenseSession defenseSession = mock(DefenseSession.class); + final SessionDetail sessionDetail = SessionDetail.create(defenseSession, 1L); + + // when + final TempCode tempCode = sessionDetail.getTempCode(JAVA); + + // then + assertThat(tempCode) + .extracting("language","sessionDetail") + .contains(JAVA, sessionDetail); + assertThat(sessionDetail.getTempCodes()).hasSize(2) + .extracting("language","sessionDetail") + .contains( + tuple(CPP, sessionDetail), + tuple(JAVA, sessionDetail) + ); + } + + @DisplayName("SessionDetail에 저장된 언어로 tempCode에 접근하여 수정할 수 있다.") + @Test + void updateTempCode() { + // given + DefenseSession defenseSession = mock(DefenseSession.class); + final SessionDetail sessionDetail = SessionDetail.create(defenseSession, 1L); + + // when + sessionDetail.updateTempCode(CPP, "newCode"); + + // then + assertThat(sessionDetail.getTempCode(CPP) + .getCode()) + .isEqualTo("newCode"); + + } + + @DisplayName("SessionDetail에 저장되지 않은 언어로 tempCode를 수정하면 새로 생성된다.") + @Test + void updateTempCodeWhenNotExists() { + // given + DefenseSession defenseSession = mock(DefenseSession.class); + final SessionDetail sessionDetail = SessionDetail.create(defenseSession, 1L); + + // when + sessionDetail.updateTempCode(JAVA, "newCode"); + + // then + assertThat(sessionDetail.getTempCodes()).hasSize(2) + .extracting("language","sessionDetail") + .contains( + tuple(CPP, sessionDetail), + tuple(JAVA, sessionDetail) + ); + + assertThat(sessionDetail.getTempCode(JAVA) + .getCode()) + .isEqualTo("newCode"); + + } + @DisplayName("SessionDetail에 tempCode를 원하는 언어로 추가할 수 있다.") + @Test + void addTempCode() { + // given + DefenseSession defenseSession = mock(DefenseSession.class); + final SessionDetail sessionDetail = SessionDetail.create(defenseSession, 1L); + + // when + sessionDetail.addTempCode(JAVA, JAVA.getInitialCode()); + + // then + assertThat(sessionDetail.getTempCodes()).hasSize(2) + .extracting("language","sessionDetail") + .contains( + tuple(CPP, sessionDetail), + tuple(JAVA, sessionDetail) + ); + + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java index d21f1ee9..4daef905 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java @@ -1,10 +1,11 @@ package kr.co.morandi.backend.domain.detail.customdefense; -import kr.co.morandi.backend.domain.defense.customdefense.CustomDefense; -import kr.co.morandi.backend.domain.defense.customdefense.CustomDefenseProblem; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.record.customdefense.CustomRecord; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefenseProblem; +import kr.co.morandi.backend.domain.detail.customdefense.model.CustomDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.customdefenserecord.model.CustomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -12,11 +13,11 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.S5; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.S5; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -38,7 +39,7 @@ void create() { CustomRecord customDefenseRecord = mock(CustomRecord.class); // when - CustomDetail customDefenseProblemRecord = CustomDetail.create(member, problem, customDefenseRecord, customDefense); + CustomDetail customDefenseProblemRecord = CustomDetail.create(member, 1L, problem, customDefenseRecord, customDefense); // then assertThat(customDefenseProblemRecord).isNotNull() @@ -61,7 +62,7 @@ void initialSolvedTimeIsOne() { CustomRecord customDefenseRecord = mock(CustomRecord.class); // when - CustomDetail customDefenseProblemRecord = CustomDetail.create(member, problem, customDefenseRecord, customDefense); + CustomDetail customDefenseProblemRecord = CustomDetail.create(member, 1L, problem, customDefenseRecord, customDefense); // then assertThat(customDefenseProblemRecord.getSolvedTime()) diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java index 2e9f2763..b4067d05 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java @@ -1,20 +1,24 @@ package kr.co.morandi.backend.domain.detail.dailydefense; -import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefenseProblem; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.record.dailydefense.DailyRecord; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.detail.dailydefense.model.DailyDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; -import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -33,7 +37,7 @@ void create() { Member member = createMember(); // when - DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, 1L, problem, DailyDefenseRecord, DailyDefense); // then assertThat(DailyDefenseProblemRecord).isNotNull() @@ -53,7 +57,7 @@ void initialIsSolvedFalse() { Member member = createMember(); // when - DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, 1L, problem, DailyDefenseRecord, DailyDefense); // then assertThat(DailyDefenseProblemRecord.getIsSolved()).isFalse(); @@ -71,7 +75,7 @@ void initialSubmitCountIsZero() { Member member = createMember(); // when - DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, 1L, problem, DailyDefenseRecord, DailyDefense); // then assertThat(DailyDefenseProblemRecord.getSubmitCount()).isZero(); @@ -89,7 +93,7 @@ void initialSolvedCodeIsSetToNull() { Member member = createMember(); // when - DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, problem, DailyDefenseRecord, DailyDefense); + DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, 1L, problem, DailyDefenseRecord, DailyDefense); // then assertThat(DailyDefenseProblemRecord.getSolvedCode()) @@ -97,8 +101,11 @@ void initialSolvedCodeIsSetToNull() { } private DailyDefense createDailyDefense() { - LocalDateTime createDate = LocalDateTime.of(2023, 3, 5, 0, 0); - return DailyDefense.create(createDate, "3월 5일 문제", createProblem()); + LocalDate createdDate = LocalDate.of(2024, 3, 1); + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblem().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap); } private List createProblem() { return List.of( diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java index c51385a0..ee80f594 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java @@ -1,15 +1,16 @@ package kr.co.morandi.backend.domain.detail.randomdefense; -import kr.co.morandi.backend.domain.defense.random.RandomDefense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.record.randomdefense.RandomRecord; +import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; +import kr.co.morandi.backend.domain.detail.randomdefense.model.RandomDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.randomrecord.model.RandomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -25,7 +26,7 @@ void create() { Member member = createMember("test"); // when - RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, 1L, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord).isNotNull() @@ -43,7 +44,7 @@ void initialSolvedTimeIsZero() { Member member = createMember("test"); // when - RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, 1L, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getSolvedTime()).isZero(); @@ -58,7 +59,7 @@ void initialIsSolvedFalse() { Member member = createMember("test"); // when - RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member,1L, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getIsSolved()).isFalse(); @@ -73,7 +74,7 @@ void initialSubmitCountIsZero() { Member member = createMember("test"); // when - RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, 1L, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getSubmitCount()).isZero(); } @@ -87,7 +88,7 @@ void initialSolvedCodeIsSetToNull() { Member member = createMember("test"); // when - RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, problem, randomDefenseRecord, randomDefense); + RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, 1L, problem, randomDefenseRecord, randomDefense); // then assertThat(randomDefenseProblemRecord.getSolvedCode()) diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java index 85a941e5..1b21902d 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java @@ -1,17 +1,17 @@ package kr.co.morandi.backend.domain.detail.randomdefense; -import kr.co.morandi.backend.domain.defense.stagedefense.StageDefense; -import kr.co.morandi.backend.domain.detail.stagedefense.StageDetail; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.record.stagedefense.StageRecord; +import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; +import kr.co.morandi.backend.domain.detail.stagedefense.model.StageDetail; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.stagerecord.model.StageRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; -import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -27,7 +27,7 @@ void initialSolvedTimeIsZero() { Member member = createMember(); // when - StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(member,1L, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord.getSolvedTime()).isEqualTo(0L); @@ -43,7 +43,7 @@ void createStageDefenseProblemRecordWithStageNumber() { Member member = createMember(); // when - StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(member, 1L, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord.getStageNumber()).isEqualTo(1L); @@ -58,7 +58,7 @@ void initialIsSolvedIsFalse() { Member member = createMember(); // when - StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(member, 1L, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) @@ -76,7 +76,7 @@ void initialSubmitCountIsZero() { Member member = createMember(); // when - StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(member, 1L, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) .extracting("submitCount") @@ -93,7 +93,7 @@ void initialSolvedCodeIsNull() { Member member = createMember(); // when - StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(member, 1L, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) @@ -111,7 +111,7 @@ void createStageDefenseProblemRecord() { Member member = createMember(); // when - StageDetail stageDefenseProblemRecord = StageDetail.create(1L, member, problem, stageDefenseRecord, randomStageDefense); + StageDetail stageDefenseProblemRecord = StageDetail.create(member, 1L, problem, stageDefenseRecord, randomStageDefense); // then assertThat(stageDefenseProblemRecord) .extracting("member", "problem", "record", "defense") diff --git a/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java b/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java index 841ff368..508354e5 100644 --- a/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java @@ -1,9 +1,11 @@ package kr.co.morandi.backend.domain.member; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.member.model.SocialType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; class MemberTest { diff --git a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java index f3129230..9796fe7e 100644 --- a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java @@ -1,12 +1,14 @@ package kr.co.morandi.backend.domain.problem; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.problem.model.ProblemStatus; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; -import static kr.co.morandi.backend.domain.problem.ProblemStatus.INIT; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.INIT; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/customdefenserecord/CustomRecordTest.java similarity index 80% rename from src/test/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/record/customdefenserecord/CustomRecordTest.java index d147adb6..4b4e13be 100644 --- a/src/test/java/kr/co/morandi/backend/domain/record/customdefense/CustomRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/record/customdefenserecord/CustomRecordTest.java @@ -1,21 +1,24 @@ -package kr.co.morandi.backend.domain.record.customdefense; +package kr.co.morandi.backend.domain.record.customdefenserecord; -import kr.co.morandi.backend.domain.defense.customdefense.CustomDefense; -import kr.co.morandi.backend.domain.defense.customdefense.CustomDefenseProblem; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefenseProblem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.customdefenserecord.model.CustomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; import java.util.List; - -import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.S5; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import java.util.Map; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.GOLD; +import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.S5; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") @@ -25,7 +28,7 @@ class CustomRecordTest { void testDateIsEqualNow() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -41,7 +44,7 @@ void testDateIsEqualNow() { void solvedCountIsZero() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -57,7 +60,7 @@ void solvedCountIsZero() { void problemCountIsEqual() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -73,7 +76,7 @@ void problemCountIsEqual() { void totalSolvedTimeIsZero() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = Member.create("user", "user@gmail.com", GOOGLE, "user", "user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -89,7 +92,7 @@ void totalSolvedTimeIsZero() { void isSolvedFalse() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = createMember("user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -111,7 +114,7 @@ void isSolvedFalse() { void solvedTimeIsZero() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = createMember("user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -132,7 +135,7 @@ void solvedTimeIsZero() { void submitCountIsZero() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = createMember("user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -153,7 +156,7 @@ void submitCountIsZero() { void solvedCodeIsNull() { // given CustomDefense customDefense = createCustomDefense(); - List problems = getCustomDefenseProblems(customDefense); + Map problems = getCustomDefenseProblems(customDefense); Member member = createMember("user"); LocalDateTime startTime = LocalDateTime.of(2024, 2, 26, 0, 0, 0, 0); @@ -181,12 +184,11 @@ private CustomDefense createCustomDefense() { return CustomDefense.create(problems, member, "custom_defense", "custom_defense", OPEN, GOLD, 60L, now); } - private List getCustomDefenseProblems(CustomDefense customDefense) { + private Map getCustomDefenseProblems(CustomDefense customDefense) { List customDefenseProblems = customDefense.getCustomDefenseProblems(); return customDefenseProblems.stream() - .map(CustomDefenseProblem::getProblem) - .toList(); + .collect(Collectors.toMap(CustomDefenseProblem::getProblemNumber, CustomDefenseProblem::getProblem)); } private Member createMember(String name) { return Member.create(name, name + "@gmail.com", GOOGLE, name, name); diff --git a/src/test/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecordTest.java deleted file mode 100644 index 7cc1d869..00000000 --- a/src/test/java/kr/co/morandi/backend/domain/record/dailydefense/DailyRecordTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package kr.co.morandi.backend.domain.record.dailydefense; - -import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.DailyDefenseProblem; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.test.context.ActiveProfiles; - -import java.time.LocalDateTime; -import java.util.List; - -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.groups.Tuple.tuple; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -@ActiveProfiles("test") -class DailyRecordTest { - @DisplayName("오늘의 문제 기록이 만들어졌을 때 푼 문제 수는 0문제 이어야 한다.") - @Test - void solvedCountIsZero() { - // given - DailyDefense DailyDefense = createDailyDefense(); - LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); - Member member = createMember("user"); - List problems = getProblemList(DailyDefense); - - // when - DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); - - // then - assertThat(DailyDefenseRecord.getSolvedCount()).isZero(); - } - @DisplayName("오늘의 문제 기록이 만들어졌을 때 전체 문제 수는 오늘의 문제에 출제된 문제 들과 같아야 한다.") - @Test - void problemCountIsEqual() { - // given - DailyDefense DailyDefense = createDailyDefense(); - LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); - Member member = createMember("user"); - List problems = getProblemList(DailyDefense); - - // when - DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); - - - // then - assertThat(DailyDefenseRecord.getProblemCount()).isEqualTo(DailyDefense.getProblemCount()); - assertThat(DailyDefenseRecord.getDetails()) - .extracting("problem", "record") - .containsExactlyInAnyOrder( - tuple(problems.get(0), DailyDefenseRecord), - tuple(problems.get(1), DailyDefenseRecord), - tuple(problems.get(2), DailyDefenseRecord) - ); - } - @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가면 예외가 발생한다.") - @Test - void recordCreateExceptionWhenOverOneDay() { - // given - LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); - DailyDefense DailyDefense = createDailyDefense(createdTime); - - Member member = createMember("user"); - List problems = getProblemList(DailyDefense); - - LocalDateTime startTime = LocalDateTime.of(2024, 3, 2, 0, 0, 0); - - // when & then - assertThatThrownBy(() -> DailyRecord.create(startTime, DailyDefense, member, problems)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("오늘의 문제 기록은 출제 시점으로부터 하루 이내에 생성되어야 합니다."); - } - @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가지 않으면 정상적으로 등록된다.") - @Test - void recordCreatedWithinOneDay() { - // given - LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); - DailyDefense DailyDefense = createDailyDefense(createdTime); - - Member member = createMember("user"); - List problems = getProblemList(DailyDefense); - - LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 23, 59, 59); - - // when - DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); - - // then - assertNotNull(DailyDefenseRecord); - } - @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 여부는 모두 오답 상태여야 한다.") - @Test - void isSolvedIsFalse() { - // given - DailyDefense DailyDefense = createDailyDefense(); - LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); - Member member = createMember("user"); - List problems = getProblemList(DailyDefense); - - // when - DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); - List contentProblemRecords = DailyDefenseRecord.getDetails(); - - // then - assertThat(contentProblemRecords) - .extracting("isSolved") - .containsExactlyInAnyOrder(false, false, false); - } - @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 제출 횟수는 모두 0회여야 한다.") - @Test - void submitCountIsZero() { - // given - DailyDefense DailyDefense = createDailyDefense(); - LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); - Member member = createMember("user"); - List problems = getProblemList(DailyDefense); - - // when - DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); - List contentProblemRecords = DailyDefenseRecord.getDetails(); - - // then - assertThat(contentProblemRecords) - .extracting("submitCount") - .containsExactlyInAnyOrder(0L, 0L, 0L); - } - @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 코드는 null 이어야 한다.") - @Test - void solvedCodeIsNull() { - // given - DailyDefense DailyDefense = createDailyDefense(); - LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); - Member member = createMember("user"); - List problems = getProblemList(DailyDefense); - - // when - DailyRecord DailyDefenseRecord = DailyRecord.create(startTime, DailyDefense, member, problems); - List contentProblemRecords = DailyDefenseRecord.getDetails(); - // then - assertThat(contentProblemRecords) - .extracting("solvedCode") - .containsExactlyInAnyOrder(null, null, null); - } - private List getProblemList(DailyDefense DailyDefense) { - return DailyDefense.getDailyDefenseProblems().stream() - .map(DailyDefenseProblem::getProblem) - .toList(); - } - private DailyDefense createDailyDefense() { - List problems = createProblems(); - LocalDateTime createdTime = LocalDateTime.of(2024, 3, 1, 0, 0, 0); - return DailyDefense.create(createdTime, "오늘의 문제 테스트", problems); - } - private DailyDefense createDailyDefense(LocalDateTime createdTime) { - List problems = createProblems(); - return DailyDefense.create(createdTime, "오늘의 문제 테스트", problems); - } - private List createProblems() { - Problem problem1 = Problem.create(1L, B5, 0L); - Problem problem2 = Problem.create(2L, S5, 0L); - Problem problem3 = Problem.create(3L, G5, 0L); - return List.of(problem1, problem2, problem3); - } - private Member createMember(String name) { - return Member.create(name, name + "@gmail.com", GOOGLE, name, name); - } -} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/record/dailyrecord/DailyRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/dailyrecord/DailyRecordTest.java new file mode 100644 index 00000000..8ac5657e --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/domain/record/dailyrecord/DailyRecordTest.java @@ -0,0 +1,193 @@ +package kr.co.morandi.backend.domain.record.dailyrecord; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ActiveProfiles("test") +class DailyRecordTest { + + @DisplayName("오늘의 문제 기록에서 세부 문제의 정답 여부를 확인할 수 있다.") + @Test + void isSolvedProblem() { + // given + DailyDefense DailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(DailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, triedProblem); + + // when + final boolean solvedProblem = dailyRecord.isSolvedProblem(2L); + + // then + assertThat(solvedProblem).isFalse(); + + } + @DisplayName("오늘의 문제 기록이 이미 있을 때, 같은 문제를 다시 시도하면 기존 문제 기록을 반환한다.") + @Test + void tryExistDetailThenReturnExistDetail() { + // given + DailyDefense DailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(DailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, triedProblem); + + // when + dailyRecord.tryMoreProblem(triedProblem); + + // then + assertThat(dailyRecord.getDetails()).hasSize(1) + .extracting("problem") + .contains(triedProblem.get(2L)); + } + + @DisplayName("오늘의 문제 기록이 만들어졌을 때 푼 문제 수는 0문제 이어야 한다.") + @Test + void solvedCountIsZero() { + // given + DailyDefense DailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map problems = getProblems(DailyDefense, 2L); + + // when + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + + // then + assertThat(dailyDefenseRecord.getSolvedCount()).isZero(); + } + + @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가면 예외가 발생한다.") + @Test + void recordCreateExceptionWhenOverOneDay() { + // given + LocalDate createdDate = LocalDate.of(2024, 3, 1); + DailyDefense DailyDefense = createDailyDefense(createdDate); + + Member member = createMember("user"); + Map problems = getProblems(DailyDefense, 2L); + + LocalDateTime startTime = LocalDateTime.of(2024, 3, 2, 0, 0, 0); + + // when & then + assertThatThrownBy(() -> DailyRecord.tryDefense(startTime, DailyDefense, member, problems)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("오늘의 문제 기록은 출제 날짜와 같은 날에 생성되어야 합니다."); + } + @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가지 않으면 정상적으로 등록된다.") + @Test + void recordCreatedWithinOneDay() { + // given + LocalDate createdDate = LocalDate.of(2024, 3, 1); + DailyDefense DailyDefense = createDailyDefense(createdDate); + + Member member = createMember("user"); + Map problems = getProblems(DailyDefense, 2L); + + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 23, 59, 59); + + // when + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + + // then + assertNotNull(dailyDefenseRecord); + } + @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 여부는 모두 오답 상태여야 한다.") + @Test + void isSolvedIsFalse() { + // given + DailyDefense DailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map problems = getProblems(DailyDefense, 2L); + + // when + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + + // then + assertThat(dailyDefenseRecord.getDetails()) + .extracting("isSolved") + .contains(false); + } + @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 제출 횟수는 모두 0회여야 한다.") + @Test + void submitCountIsZero() { + // given + DailyDefense DailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map problems = getProblems(DailyDefense, 2L); + + // when + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + + // then + assertThat(dailyDefenseRecord.getDetails()) + .extracting("submitCount") + .containsExactlyInAnyOrder(0L); + } + @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 코드는 null 이어야 한다.") + @Test + void solvedCodeIsNull() { + // given + DailyDefense DailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map problems = getProblems(DailyDefense,2L); + + // when + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + + // then + assertThat(dailyDefenseRecord.getDetails()) + .extracting("solvedCode") + .contains((String)null); + } + private Map getProblems(DailyDefense DailyDefense, Long problemNumber) { + return DailyDefense.getDailyDefenseProblems().stream() + .filter(p -> p.getProblemNumber().equals(problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + private DailyDefense createDailyDefense() { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + LocalDate createdDate = LocalDate.of(2024, 3, 1); + return DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap); + } + private DailyDefense createDailyDefense(LocalDate createdDate) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return List.of(problem1, problem2, problem3); + } + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java index 84e485e1..fb0b24b6 100644 --- a/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java @@ -1,18 +1,19 @@ package kr.co.morandi.backend.domain.record.randomdefense; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.random.RandomDefense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; +import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.randomrecord.model.RandomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; -import java.util.List; +import java.util.Map; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") @@ -22,7 +23,7 @@ class RandomRecordTest { void solvedCountIsZero() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -38,7 +39,7 @@ void solvedCountIsZero() { void problemCountIsEqual() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -55,7 +56,7 @@ void problemCountIsEqual() { void totalSolvedTimeIsZero() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -71,7 +72,7 @@ void totalSolvedTimeIsZero() { void testDateIsEqualTestDate() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -87,7 +88,7 @@ void testDateIsEqualTestDate() { void isSolvedIsFalse() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -110,7 +111,7 @@ void isSolvedIsFalse() { void submitCountIsZero() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -133,7 +134,7 @@ void submitCountIsZero() { void solvedCodeIsNull() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -156,7 +157,7 @@ void solvedCodeIsNull() { void solvedTimeIsZero() { // given RandomDefense randomDefense = createRandomDefense(); - List problems = getProblemsByRandom(); + Map problems = getProblemsByRandom(); Member member = createMember("user"); LocalDateTime now = LocalDateTime.of(2024, 3, 1, 0,0,0); @@ -181,11 +182,17 @@ private RandomDefense createRandomDefense() { private Member createMember(String name) { return Member.create(name, name + "@gmail.com", GOOGLE, name, name); } - private List getProblemsByRandom() { + private Map getProblemsByRandom() { Problem problem1 = Problem.create(1L, B5, 0L); Problem problem2 = Problem.create(2L, S5, 0L); Problem problem3 = Problem.create(3L, G5, 0L); Problem problem4 = Problem.create(4L, P5, 0L); - return List.of(problem1, problem2, problem3, problem4); + + return Map.of( + 1L, problem1, + 2L, problem2, + 3L, problem3, + 4L, problem4 + ); } } \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/StageRecordTest.java b/src/test/java/kr/co/morandi/backend/domain/record/stagerecord/StageRecordTest.java similarity index 90% rename from src/test/java/kr/co/morandi/backend/domain/record/randomdefense/StageRecordTest.java rename to src/test/java/kr/co/morandi/backend/domain/record/stagerecord/StageRecordTest.java index 72c43f90..1cced10a 100644 --- a/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/StageRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/domain/record/stagerecord/StageRecordTest.java @@ -1,19 +1,19 @@ -package kr.co.morandi.backend.domain.record.randomdefense; +package kr.co.morandi.backend.domain.record.stagerecord; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.stagedefense.StageDefense; -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.record.stagedefense.StageRecord; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.stagerecord.model.StageRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapterTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapterTest.java new file mode 100644 index 00000000..e9e20115 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapterTest.java @@ -0,0 +1,126 @@ +package kr.co.morandi.backend.infrastructure.adapter.defense.defense.dailydefense; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class DailyDefenseProblemAdapterTest { + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyDefenseProblemAdapter dailyDefenseProblemAdapter; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyDefenseProblemRepository dailyDefenseProblemRepository; + + @AfterEach + void tearDown() { + dailyDefenseProblemRepository.deleteAllInBatch(); + dailyDefenseRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + } + + @DisplayName("오늘의 문제로 출제된 적이 있는 문제는 출제되지 않는다.") + @Test + void getDailyDefenseProblemWithAlreadySolved() { + // given + createProblems(); + RandomCriteria randomCriteria1 = RandomCriteria.builder() + .minSolvedCount(500L) + .maxSolvedCount(1500L) + .difficultyRange(RandomCriteria.DifficultyRange.of(B5, B1)) + .build(); + RandomCriteria randomCriteria2 = RandomCriteria.builder() + .minSolvedCount(1500L) + .maxSolvedCount(3500L) + .difficultyRange(RandomCriteria.DifficultyRange.of(S5, G1)) + .build(); + Map request = Map.of(1L, randomCriteria2, 2L, randomCriteria1); + final Map dailyDefenseProblem = dailyDefenseProblemAdapter.getDailyDefenseProblem(request); + + LocalDate yesterday = LocalDate.of(2021, 1, 1); + final DailyDefense dailyDefense = DailyDefense.create(yesterday, "어제 오늘의 문제", dailyDefenseProblem); + dailyDefenseRepository.save(dailyDefense); + + Map newRequest = Map.of(1L, randomCriteria2); + + + // when + final Map dailyDefenseProblem2 = dailyDefenseProblemAdapter.getDailyDefenseProblem(newRequest); + + // then + assertThat(dailyDefenseProblem2).hasSize(1); + + // 같은 범위에 2개의 문제가 있는데, 출제되면 서로 다른 문제가 출제될 테니깐 + assertThat(dailyDefenseProblem.get(1L).getProblemTier()).isNotEqualTo(dailyDefenseProblem2.get(1L).getProblemTier()); + } + @DisplayName("오늘의 문제에 포함되는 문제들을 의도하는 조건대로 출제할 수 있다.") + @Test + void getDailyDefenseProblem() { + // given + createProblems(); + RandomCriteria randomCriteria1 = RandomCriteria.builder() + .minSolvedCount(500L) + .maxSolvedCount(1500L) + .difficultyRange(RandomCriteria.DifficultyRange.of(B5, B1)) + .build(); + RandomCriteria randomCriteria2 = RandomCriteria.builder() + .minSolvedCount(1500L) + .maxSolvedCount(2500L) + .difficultyRange(RandomCriteria.DifficultyRange.of(S5, S1)) + .build(); + + Map request = Map.of(1L, randomCriteria1, 2L, randomCriteria2); + + // when + final Map dailyDefenseProblem = dailyDefenseProblemAdapter.getDailyDefenseProblem(request); + + // then + assertThat(dailyDefenseProblem).hasSize(2); + assertThat(dailyDefenseProblem.entrySet()) + .extracting(Map.Entry::getKey, entry -> entry.getValue().getProblemTier(), entry -> entry.getValue().getSolvedCount()) + .containsExactlyInAnyOrder( + tuple(2L, S5, 2000L), + tuple(1L, B5, 1000L) + ); + } + + private void createProblems() { + Problem problem1 = Problem.create(1L, B5, 1000L); + problem1.activate(); + + Problem problem2 = Problem.create(2L, S5, 2000L); + problem2.activate(); + + Problem problem3 = Problem.create(3L, G5, 3000L); + problem3.activate(); + + problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapterTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapterTest.java new file mode 100644 index 00000000..aea85a41 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapterTest.java @@ -0,0 +1,126 @@ +package kr.co.morandi.backend.infrastructure.adapter.defense.record.dailyrecord; + +import kr.co.morandi.backend.application.port.out.record.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord.DailyRecordRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class DailyRecordAdapterTest { + + @Autowired + private DailyRecordPort dailyRecordPort; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private MemberRepository memberRepository; + + @AfterEach + void tearDown() { + dailyRecordRepository.deleteAll(); + dailyDefenseRepository.deleteAll(); + problemRepository.deleteAll(); + memberRepository.deleteAll(); + } + + @DisplayName("오늘 날짜에 해당하는 DailyRecord가 존재할 때 찾아올 수 있다.") + @Test + void findDailyRecordByMemberAndDate() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + + final Member member = createMember(); + tryDailyDefense(today, member); + + // when + Optional foundDailyRecord = dailyRecordPort.findDailyRecord(member, today.toLocalDate()); + + // then + assertThat(foundDailyRecord).isPresent() + .get() + .extracting("testDate", "problemCount") + .contains(today, 1); + + } + + @DisplayName("오늘 날짜에 해당하는 DailyRecord가 없을 때 null을 반한한다.") + @Test + void nullWhenDailyRecordNotExists() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + + final Member member = createMember(); + + // when + Optional foundDailyRecord = dailyRecordPort.findDailyRecord(member, today.toLocalDate()); + + // then + assertThat(foundDailyRecord).isNotPresent(); + + } + + private void tryDailyDefense(LocalDateTime today, Member member) { + final DailyDefense dailyDefense = createDailyDefense(today.toLocalDate()); + dailyDefenseRepository.save(dailyDefense); + + DailyRecord dailyRecord = DailyRecord.tryDefense(today, dailyDefense, member, getProblem(dailyDefense, 2L)); + dailyRecordRepository.save(dailyRecord); + } + + private Map getProblem(DailyDefense dailyDefense, Long problemNumber) { + return dailyDefense.getDailyDefenseProblems().stream() + .filter(problem -> Objects.equals(problem.getProblemNumber(), problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + + private DailyDefense createDailyDefense(LocalDate createdDate) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapterTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapterTest.java new file mode 100644 index 00000000..56e23c11 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapterTest.java @@ -0,0 +1,76 @@ +package kr.co.morandi.backend.infrastructure.adapter.defensesession; + +import kr.co.morandi.backend.application.port.out.defensemanagement.defensesession.DefenseSessionPort; +import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; +import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.sessiondetail.SessionDetailRepository; +import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.Set; + +import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class DefenseSessionAdapterTest { + + @Autowired + private DefenseSessionPort defenseSessionPort; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private DefenseSessionRepository defenseSessionRepository; + + @Autowired + private SessionDetailRepository sessionDetailRepository; + @AfterEach + void tearDown() { + sessionDetailRepository.deleteAll(); + defenseSessionRepository.deleteAllInBatch(); + memberRepository.deleteAll(); + } + + @DisplayName("DailyDefense 세션을 조회할 수 있다.") + @Test + void findDailyDefenseSession() { + // given + Long recordId = 1L; + Member member = createMember(); + + Set problemNumbers = Set.of(2L); + LocalDateTime startDateTime = LocalDateTime.of(2021, 10, 1, 12, 0); + LocalDateTime endDateTime = LocalDateTime.of(2021, 10, 1, 23, 59); + + final DefenseSession session = DefenseSession.startSession(member, recordId, DAILY, problemNumbers, startDateTime, endDateTime); + defenseSessionPort.saveDefenseSession(session); + + // when + final Optional dailyDefenseSession = defenseSessionPort.findTodaysDailyDefenseSession(member, startDateTime); + + // then + assertThat(dailyDefenseSession).isPresent() + .get() + .extracting("lastAccessDateTime", "lastAccessProblemNumber", "endDateTime", "defenseType") + .containsExactly(startDateTime, 2L, endDateTime, DAILY); + + } + + + private Member createMember() { + return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/problem/ProblemAdapterTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/problem/ProblemAdapterTest.java new file mode 100644 index 00000000..5572d951 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/infrastructure/adapter/problem/ProblemAdapterTest.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend.infrastructure.adapter.problem; + +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ProblemAdapterTest { + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/config/AlgorithmInitializerTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializerTest.java similarity index 90% rename from src/test/java/kr/co/morandi/backend/config/AlgorithmInitializerTest.java rename to src/test/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializerTest.java index 0c455296..60bf78eb 100644 --- a/src/test/java/kr/co/morandi/backend/config/AlgorithmInitializerTest.java +++ b/src/test/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializerTest.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.config; +package kr.co.morandi.backend.infrastructure.config; -import kr.co.morandi.backend.domain.algorithm.Algorithm; -import kr.co.morandi.backend.domain.algorithm.AlgorithmRepository; +import kr.co.morandi.backend.domain.algorithm.model.Algorithm; +import kr.co.morandi.backend.infrastructure.persistence.algorithm.AlgorithmRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepositoryTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java similarity index 87% rename from src/test/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java index 52d68c30..21655eea 100644 --- a/src/test/java/kr/co/morandi/backend/domain/algorithm/AlgorithmRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java @@ -1,5 +1,7 @@ -package kr.co.morandi.backend.domain.algorithm; +package kr.co.morandi.backend.infrastructure.persistence.algorithm; +import kr.co.morandi.backend.domain.algorithm.model.Algorithm; +import kr.co.morandi.backend.infrastructure.persistence.algorithm.AlgorithmRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java similarity index 77% rename from src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java index 821b64bf..6db35913 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java @@ -1,9 +1,12 @@ -package kr.co.morandi.backend.domain.defense.customdefense; - -import kr.co.morandi.backend.domain.member.Member; -import kr.co.morandi.backend.domain.member.MemberRepository; -import kr.co.morandi.backend.domain.problem.Problem; -import kr.co.morandi.backend.domain.problem.ProblemRepository; +package kr.co.morandi.backend.infrastructure.persistence.customdefense; + +import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; +import kr.co.morandi.backend.infrastructure.persistence.defense.customdefense.CustomDefenseProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.defense.customdefense.CustomDefenseRepository; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,11 +17,11 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.defense.customdefense.DefenseTier.*; -import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.CLOSE; -import static kr.co.morandi.backend.domain.defense.customdefense.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.*; +import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.CLOSE; +import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepositoryTest.java new file mode 100644 index 00000000..e7b6f1ef --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepositoryTest.java @@ -0,0 +1,77 @@ +package kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense; + +import kr.co.morandi.backend.domain.defense.Defense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class DailyDefenseProblemRepositoryTest { + + @Autowired + private DailyDefenseProblemRepository dailyDefenseProblemRepository; + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + @Autowired + private ProblemRepository problemRepository; + + @AfterEach + void tearDown() { + dailyDefenseProblemRepository.deleteAllInBatch(); + dailyDefenseRepository.deleteAllInBatch(); + problemRepository.deleteAllInBatch(); + } + + @DisplayName("defense 타입으로 DailyDefenseProblem을 가져올 수 있다.") + @Test + void findAllProblemsContainsDefenseId() { + // given + LocalDate defenseDate = LocalDate.of(2021, 1, 1); + Defense defense = createDailyDefense(defenseDate); + + // when + List dailyDefenseProblems = dailyDefenseProblemRepository.findAllProblemsContainsDefenseId(defense.getDefenseId()); + + // then + assertThat(dailyDefenseProblems).hasSize(3) + .extracting("problem.baekjoonProblemId", "problem.problemTier", "problem.solvedCount") + .containsExactlyInAnyOrder( + tuple(1L, B5, 0L), + tuple(2L, S5, 0L), + tuple(3L, G5, 0L) + ); + + } + private DailyDefense createDailyDefense(LocalDate date) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return dailyDefenseRepository.save(DailyDefense.create(date, "오늘의 문제 테스트", problemMap)); + } + + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/randomdefense/RandomDefenseRepositoryTest.java similarity index 89% rename from src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/randomdefense/RandomDefenseRepositoryTest.java index 2f252942..882659ad 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/randomdefense/RandomDefenseRepositoryTest.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.defense.randomdefense; +package kr.co.morandi.backend.infrastructure.persistence.defense.randomdefense; -import kr.co.morandi.backend.domain.defense.random.RandomDefense; -import kr.co.morandi.backend.domain.defense.random.RandomDefenseRepository; -import kr.co.morandi.backend.domain.defense.random.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; +import kr.co.morandi.backend.infrastructure.persistence.defense.random.RandomDefenseRepository; +import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,7 +13,7 @@ import java.util.List; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java new file mode 100644 index 00000000..e1676b74 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.infrastructure.persistence.detail.dailydetail; + +import kr.co.morandi.backend.domain.detail.dailydefense.model.DailyDetail; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DailyDetailRepository extends JpaRepository { +} diff --git a/src/test/java/kr/co/morandi/backend/domain/member/MemberRepositoryTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepositoryTest.java similarity index 88% rename from src/test/java/kr/co/morandi/backend/domain/member/MemberRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepositoryTest.java index 7cc5c494..1123415a 100644 --- a/src/test/java/kr/co/morandi/backend/domain/member/MemberRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepositoryTest.java @@ -1,5 +1,7 @@ -package kr.co.morandi.backend.domain.member; +package kr.co.morandi.backend.infrastructure.persistence.member; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -8,10 +10,9 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepositoryTest.java similarity index 51% rename from src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepositoryTest.java index 6a0b07de..864a0f5c 100644 --- a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepositoryTest.java @@ -1,16 +1,19 @@ -package kr.co.morandi.backend.domain.problem; +package kr.co.morandi.backend.infrastructure.persistence.problem; +import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; +import kr.co.morandi.backend.domain.problem.model.Problem; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.PageRequest; import org.springframework.test.context.ActiveProfiles; import java.util.List; -import static kr.co.morandi.backend.domain.defense.tier.ProblemTier.*; -import static kr.co.morandi.backend.domain.problem.ProblemStatus.ACTIVE; +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; @@ -26,6 +29,34 @@ void tearDown() { problemRepository.deleteAllInBatch(); } + + @DisplayName("startTier와 endTier사이고, ACTIVE, dailyDefenseProblem에 속하지 않은 문제들을 가져올 수 있다.") + @Test + void findDailyDefenseProblems() { + // given + Problem problem1 = Problem.create(1L, B5, 1000L); + Problem problem2 = Problem.create(2L, S5, 2000L); + problem2.activate(); + Problem problem3 = Problem.create(3L, G5, 3000L); + + problemRepository.saveAll(List.of(problem1, problem2, problem3)); + + List tierRange = ProblemTier.tierRangeOf(S5, S1); + Long startSolvedCount = 1500L; + Long endSolvedCount = 2500L; + + PageRequest pageRequest = PageRequest.of(0, 1); + + List problems = problemRepository.getDailyDefenseProblems(tierRange, startSolvedCount, endSolvedCount, pageRequest); + + + // then + assertThat(problems).hasSize(1) + .allMatch(problem -> problem.getProblemTier().compareTo(S5) >= 0 + && problem.getProblemTier().compareTo(S1) <= 0); + + } + @DisplayName("활성화된 문제들의 리스트를 조회할 수 있다.") @Test void findAllByProblemStatus() { diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepositoryTest.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepositoryTest.java new file mode 100644 index 00000000..bed2c91b --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepositoryTest.java @@ -0,0 +1,156 @@ +package kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord; + +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@SpringBootTest +@ActiveProfiles("test") +class DailyRecordRepositoryTest { + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @Autowired + private DailyDefenseProblemRepository dailyDefenseProblemRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private MemberRepository memberRepository; + + + @AfterEach + void tearDown() { + dailyRecordRepository.deleteAll(); + dailyDefenseProblemRepository.deleteAllInBatch(); + dailyDefenseRepository.deleteAllInBatch(); + dailyRecordRepository.deleteAllInBatch(); + problemRepository.deleteAll(); + memberRepository.deleteAll(); + } + + @DisplayName("원하는 recordId에 해당하는 DailyRecord가 존재할 때 찾아올 수 있다.") + @Test + void findDailyRecordWithRecordId() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + + final Member member = createMember(); + final DailyRecord dailyRecord = tryDailyDefense(today, member); + + // when + Optional maybeDailyRecord = dailyRecordRepository.findDailyRecordByRecordId(member, dailyRecord.getRecordId(), today.toLocalDate()); + + // then + assertThat(maybeDailyRecord).isPresent() + .get() + .extracting("testDate", "problemCount") + .contains(today, 1); + + } + + + // TODO fetch join이 정상적으로 되는지 확인하는 테스트코드 작성 + @DisplayName("오늘 날짜에 해당하는 DailyRecord가 존재할 때 문제 리스트까지 함께 가져올 수 있다.") + @Test + void findDailyRecordByMemberAndDateWithFetchJoin() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + + final Member member = createMember(); + tryDailyDefense(today, member); + + // when + DailyRecord dailyRecord = dailyRecordRepository.findDailyRecordByMemberAndDate(member, today.toLocalDate()) + .orElse(null); + + // then + assertThat(dailyRecord).isNotNull(); + assertThat(dailyRecord.getDetails()).hasSize(1) + .extracting("problemNumber","problem.baekjoonProblemId") + .contains( + tuple(2L,2L) + ); + } + @DisplayName("오늘 날짜에 해당하는 DailyRecord가 존재할 때 찾아올 수 있다.") + @Test + void findDailyRecordByMemberAndDate() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + + final Member member = createMember(); + tryDailyDefense(today, member); + + // when + Optional maybeDailyRecord = dailyRecordRepository.findDailyRecordByMemberAndDate(member, today.toLocalDate()); + + // then + assertThat(maybeDailyRecord).isPresent() + .get() + .extracting("testDate", "problemCount") + .contains(today, 1); + + } + + private DailyRecord tryDailyDefense(LocalDateTime today, Member member) { + final DailyDefense dailyDefense = createDailyDefense(today.toLocalDate()); + + DailyRecord dailyRecord = DailyRecord.tryDefense(today, dailyDefense, member, getProblem(dailyDefense, 2L)); + return dailyRecordRepository.save(dailyRecord); + } + + private Map getProblem(DailyDefense dailyDefense, Long problemNumber) { + return dailyDefense.getDailyDefenseProblems().stream() + .filter(problem -> Objects.equals(problem.getProblemNumber(), problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + + private DailyDefense createDailyDefense(LocalDate createdDate) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return dailyDefenseRepository.save(DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + } + +} \ No newline at end of file From f3b6aef3d6af1da979f7fd8cb4ad6ef7b6e73293 Mon Sep 17 00:00:00 2001 From: Jeong Yong Choi <51875177+aj4941@users.noreply.github.com> Date: Fri, 29 Mar 2024 14:42:01 +0900 Subject: [PATCH 09/14] =?UTF-8?q?=ED=97=A5=EC=82=AC=EA=B3=A0=EB=82=A0=20?= =?UTF-8?q?=EC=95=84=ED=82=A4=ED=85=8D=EC=B2=98=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :package: 헥사고날 아키텍처 구조에 따른 패키지 구조 변경 * :package: Global 패키지를 common으로 이름 변경 * :package: Addport, outputport를 port.in, port.out 으로 변경 * :package: 테스트 코드 관련 패키지 구조 변경 --- .../dailydefense/DailyDefensePort.java | 12 ------ .../dailydefense/DailyDefenseProblemPort.java | 11 ------ .../sessiondetail/SessionDetailPort.java | 10 ----- .../config/JpaAuditingConfig.java | 2 +- .../{domain => common/model}/BaseEntity.java | 2 +- .../out/dailydefense/DailyDefensePort.java | 12 ++++++ .../dailydefense/DailyDefenseProblemPort.java | 11 ++++++ .../domain/model/customdefense}/BookMark.java | 8 ++-- .../customdefense}/ContentMemberLikes.java | 8 ++-- .../model/customdefense}/CustomDefense.java | 11 +++--- .../customdefense}/CustomDefenseProblem.java | 6 +-- .../model/customdefense}/Visibility.java | 2 +- .../model/dailydefense}/DailyDefense.java | 10 ++--- .../dailydefense}/DailyDefenseProblem.java | 6 +-- .../domain/model}/defense/Defense.java | 8 ++-- .../domain/model/defense}/DefenseTier.java | 2 +- .../domain/model/defense/DefenseType.java | 6 +++ .../domain/model/defense}/ProblemTier.java | 3 +- .../domain/model/defense}/RandomCriteria.java | 3 +- .../ProblemGenerationStrategy.java | 14 +++++++ .../randomdefense}/model/RandomDefense.java | 8 ++-- .../stagedefense/model/StageDefense.java | 8 ++-- .../customdefense/CustomDefenseStrategy.java | 24 ++++++++++++ .../DailyDefenseGenerationService.java | 18 ++++----- .../dailydefense}/DailyDefenseStrategy.java | 16 ++++---- .../defense}/ProblemGenerationService.java | 10 ++--- .../dailydefense/DailyDefenseAdapter.java | 10 ++--- .../DailyDefenseProblemAdapter.java | 12 +++--- .../config/defense}/SchedulingConfig.java | 2 +- .../customdefense/BookMarkRepository.java | 7 ++++ .../ContentMemberLikesRepository.java | 7 ++++ .../CustomDefenseProblemRepository.java | 7 ++++ .../CustomDefenseRepository.java | 11 ++++++ .../DailyDefenseProblemRepository.java | 4 +- .../dailydefense/DailyDefenseRepository.java | 6 +-- .../dailydefense/DailyDetailRepository.java | 7 ++++ .../defense/DefenseRepository.java | 7 ++++ .../RandomDefenseRepository.java | 7 ++++ .../stagedefense/StageDefenseRepository.java | 7 ++++ .../DailyDefenseGenerationScheduler.java | 4 +- .../port/out/session}/DefenseSessionPort.java | 6 +-- .../port/out/session/SessionDetailPort.java | 10 +++++ .../StartDailyDefenseServiceRequest.java | 2 +- .../session}/DefenseProblemResponse.java | 16 ++++---- .../StartDailyDefenseServiceResponse.java | 12 +++--- .../DailyDefenseManagementService.java | 28 +++++++------- .../domain/model/session}/DefenseSession.java | 16 +++----- .../domain/model/session}/ExamStatus.java | 2 +- .../domain/model/session}/SessionDetail.java | 9 ++--- .../model}/tempcode/model/Language.java | 2 +- .../model}/tempcode/model/TempCode.java | 6 +-- .../session}/DefenseSessionAdapter.java | 12 +++--- .../session/DefenseSessionRepository.java | 8 ++-- .../session/SessionDetailRepository.java | 7 ++++ .../tempcode/TempCodeRepository.java | 7 ++++ .../StartDailyDefenseRequest.java | 4 +- .../out}/dailyrecord/DailyRecordPort.java | 6 +-- .../customdefense_record}/CustomDetail.java | 12 +++--- .../customdefense_record}/CustomRecord.java | 13 +++---- .../dailydefense_record}/DailyDetail.java | 12 +++--- .../dailydefense_record}/DailyRecord.java | 15 ++++---- .../randomdefense_record}/RandomDetail.java | 12 +++--- .../randomdefense_record}/RandomRecord.java | 13 +++---- .../domain/model/record}/Detail.java | 11 +++--- .../domain/model}/record/Record.java | 11 +++--- .../domain/model/record}/SubmitCode.java | 5 +-- .../stagedefense_record}/StageDetail.java | 12 +++--- .../stagedefense_record}/StageRecord.java | 11 +++--- .../dailydefense}/DailyRecordAdapter.java | 10 ++--- .../CustomDefenseRecordRepository.java | 7 ++++ .../DailyRecordRepository.java | 6 +-- .../backend/domain/defense/DefenseType.java | 6 --- .../service/CustomDefenseStrategy.java | 24 ------------ .../ProblemGenerationStrategy.java | 14 ------- .../bookmark/BookMarkRepository.java | 7 ---- .../defense/DefenseRepository.java | 7 ---- .../CustomDefenseProblemRepository.java | 7 ---- .../CustomDefenseRepository.java | 11 ------ .../random/RandomDefenseRepository.java | 7 ---- .../stagedefense/StageDefenseRepository.java | 7 ---- .../SessionDetailRepository.java | 7 ---- .../tempcode/TempCodeRepository.java | 7 ---- .../ContentMemberLikesRepository.java | 7 ---- .../ProblemAlgorithmRepository.java | 7 ---- .../CustomDefenseRecordRepository.java | 7 ---- .../domain/model/member}/Member.java | 4 +- .../domain/model/member}/SocialType.java | 2 +- .../persistence/member/MemberRepository.java | 4 +- .../domain/model/algorithm}/Algorithm.java | 4 +- .../domain/model/problem}/Problem.java | 8 ++-- .../model/problem}/ProblemAlgorithm.java | 7 ++-- .../domain/model/problem}/ProblemStatus.java | 2 +- .../initializer}/AlgorithmInitializer.java | 6 +-- .../algorithm/AlgorithmRepository.java | 4 +- .../problem/ProblemAlgorithmRepository.java | 7 ++++ .../problem/ProblemRepository.java | 8 ++-- .../customdefense/CustomDefenseTest.java | 16 ++++---- .../customdefense/CustomDetailTest.java | 24 ++++++------ .../dailydefense/DailyDefenseProblemTest.java | 9 ++--- .../model}/dailydefense/DailyDefenseTest.java | 10 ++--- .../model}/dailydefense/DailyDetailTest.java | 20 +++++----- .../model/defense}/DifficultyRangeTest.java | 10 ++--- .../model/defense}/RandomCriteriaTest.java | 6 +-- .../randomdefense/RandomDefenseTest.java | 10 ++--- .../randomdefense/RandomDetailTest.java | 16 ++++---- .../model}/stagedefense/StageDefenseTest.java | 10 ++--- .../model/stagedefense}/StageDetailTest.java | 18 ++++----- .../DailyDefenseGenerationServiceTest.java | 19 +++++----- .../ProblemGenerationServiceTest.java | 18 ++++----- .../DailyDefenseProblemAdapterTest.java | 20 +++++----- .../algorithm/AlgorithmRepositoryTest.java | 6 +-- .../CustomDefenseRepositoryTest.java | 28 +++++++------- .../DailyDefenseProblemRepositoryTest.java | 16 ++++---- .../RandomDefenseRepositoryTest.java | 10 ++--- .../out/session}/DefenseSessionPortTest.java | 31 ++++++++-------- .../DailyDefenseManagementServiceTest.java | 37 ++++++++++--------- .../model/session}/DefenseSessionTest.java | 25 +++++++------ .../model/session}/SessionDetailTest.java | 11 +++--- .../session}/DefenseSessionAdapterTest.java | 20 +++++----- .../CustomRecordTest.java | 22 +++++------ .../dailydefense_record}/DailyRecordTest.java | 16 ++++---- .../RandomRecordTest.java | 16 ++++---- .../stagedefense_record}/StageRecordTest.java | 18 ++++----- .../DailyRecordAdapterTest.java | 28 +++++++------- .../DailyRecordRepositoryTest.java | 27 +++++++------- .../dailydetail/DailyDetailRepository.java | 7 ---- .../domain/model}/member/MemberTest.java | 8 ++-- .../member/MemberRepositoryTest.java | 8 ++-- .../domain/model}/problem/ProblemTest.java | 10 ++--- .../adapter/problem/ProblemAdapterTest.java | 2 +- .../config/AlgorithmInitializerTest.java | 7 ++-- .../problem/ProblemRepositoryTest.java | 11 +++--- 132 files changed, 689 insertions(+), 694 deletions(-) delete mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java delete mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java delete mode 100644 src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java rename src/main/java/kr/co/morandi/backend/{infrastructure => common}/config/JpaAuditingConfig.java (79%) rename src/main/java/kr/co/morandi/backend/{domain => common/model}/BaseEntity.java (94%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefensePort.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java rename src/main/java/kr/co/morandi/backend/{domain/bookmark/model => defense_information/domain/model/customdefense}/BookMark.java (63%) rename src/main/java/kr/co/morandi/backend/{domain/contentmemberlikes/model => defense_information/domain/model/customdefense}/ContentMemberLikes.java (68%) rename src/main/java/kr/co/morandi/backend/{domain/defense/customdefense/model => defense_information/domain/model/customdefense}/CustomDefense.java (85%) rename src/main/java/kr/co/morandi/backend/{domain/defense/customdefense/model => defense_information/domain/model/customdefense}/CustomDefenseProblem.java (86%) rename src/main/java/kr/co/morandi/backend/{domain/defense/customdefense/model => defense_information/domain/model/customdefense}/Visibility.java (71%) rename src/main/java/kr/co/morandi/backend/{domain/defense/dailydefense/model => defense_information/domain/model/dailydefense}/DailyDefense.java (83%) rename src/main/java/kr/co/morandi/backend/{domain/defense/dailydefense/model => defense_information/domain/model/dailydefense}/DailyDefenseProblem.java (85%) rename src/main/java/kr/co/morandi/backend/{domain => defense_information/domain/model}/defense/Defense.java (79%) rename src/main/java/kr/co/morandi/backend/{domain/defense/customdefense/model => defense_information/domain/model/defense}/DefenseTier.java (76%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/DefenseType.java rename src/main/java/kr/co/morandi/backend/{domain/defense/tier/model => defense_information/domain/model/defense}/ProblemTier.java (90%) rename src/main/java/kr/co/morandi/backend/{domain/defense/random/model/randomcriteria => defense_information/domain/model/defense}/RandomCriteria.java (94%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/domain/model/problem_generation_strategy/ProblemGenerationStrategy.java rename src/main/java/kr/co/morandi/backend/{domain/defense/random => defense_information/domain/model/randomdefense}/model/RandomDefense.java (82%) rename src/main/java/kr/co/morandi/backend/{domain/defense => defense_information/domain/model}/stagedefense/model/StageDefense.java (78%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/domain/service/customdefense/CustomDefenseStrategy.java rename src/main/java/kr/co/morandi/backend/{domain/defense/dailydefense/service => defense_information/domain/service/dailydefense}/DailyDefenseGenerationService.java (77%) rename src/main/java/kr/co/morandi/backend/{domain/defense/dailydefense/service => defense_information/domain/service/dailydefense}/DailyDefenseStrategy.java (54%) rename src/main/java/kr/co/morandi/backend/{domain/defense/problemgenerationstrategy/service => defense_information/domain/service/defense}/ProblemGenerationService.java (60%) rename src/main/java/kr/co/morandi/backend/{infrastructure/adapter/defense/defense => defense_information/infrastructure/adapter}/dailydefense/DailyDefenseAdapter.java (60%) rename src/main/java/kr/co/morandi/backend/{infrastructure/adapter/defense/defense => defense_information/infrastructure/adapter}/dailydefense/DailyDefenseProblemAdapter.java (75%) rename src/main/java/kr/co/morandi/backend/{infrastructure/config => defense_information/infrastructure/config/defense}/SchedulingConfig.java (70%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/BookMarkRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/ContentMemberLikesRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseProblemRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepository.java rename src/main/java/kr/co/morandi/backend/{infrastructure/persistence/defense => defense_information/infrastructure/persistence}/dailydefense/DailyDefenseProblemRepository.java (72%) rename src/main/java/kr/co/morandi/backend/{infrastructure/persistence/defense => defense_information/infrastructure/persistence}/dailydefense/DailyDefenseRepository.java (67%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDetailRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/defense/DefenseRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/stagedefense/StageDefenseRepository.java rename src/main/java/kr/co/morandi/backend/{ => defense_information}/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java (80%) rename src/main/java/kr/co/morandi/backend/{application/port/out/defensemanagement/defensesession => defense_management/application/port/out/session}/DefenseSessionPort.java (52%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/SessionDetailPort.java rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement/management/request => defense_management/application/request/session}/StartDailyDefenseServiceRequest.java (85%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement/management/response => defense_management/application/response/session}/DefenseProblemResponse.java (78%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement/management/response => defense_management/application/response/session}/StartDailyDefenseServiceResponse.java (79%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement/management/service => defense_management/application/service/session}/DailyDefenseManagementService.java (72%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement/session/model => defense_management/domain/model/session}/DefenseSession.java (84%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement/session/model => defense_management/domain/model/session}/ExamStatus.java (50%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement/sessiondetail/model => defense_management/domain/model/session}/SessionDetail.java (88%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement => defense_management/domain/model}/tempcode/model/Language.java (88%) rename src/main/java/kr/co/morandi/backend/{domain/defensemanagement => defense_management/domain/model}/tempcode/model/TempCode.java (87%) rename src/main/java/kr/co/morandi/backend/{infrastructure/adapter/defensesession => defense_management/infrastructure/adapter/session}/DefenseSessionAdapter.java (55%) rename src/main/java/kr/co/morandi/backend/{infrastructure/persistence/defensemanagement => defense_management/infrastructure/persistence}/session/DefenseSessionRepository.java (65%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/SessionDetailRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/tempcode/TempCodeRepository.java rename src/main/java/kr/co/morandi/backend/{web/defensemanagement/dailydefense/dto/request => defense_management/infrastructure/request/dailydefense}/StartDailyDefenseRequest.java (74%) rename src/main/java/kr/co/morandi/backend/{application/port/out/record => defense_record/application/port/out}/dailyrecord/DailyRecordPort.java (56%) rename src/main/java/kr/co/morandi/backend/{domain/detail/customdefense/model => defense_record/domain/model/customdefense_record}/CustomDetail.java (68%) rename src/main/java/kr/co/morandi/backend/{domain/record/customdefenserecord/model => defense_record/domain/model/customdefense_record}/CustomRecord.java (73%) rename src/main/java/kr/co/morandi/backend/{domain/detail/dailydefense/model => defense_record/domain/model/dailydefense_record}/DailyDetail.java (64%) rename src/main/java/kr/co/morandi/backend/{domain/record/dailyrecord/model => defense_record/domain/model/dailydefense_record}/DailyRecord.java (82%) rename src/main/java/kr/co/morandi/backend/{domain/detail/randomdefense/model => defense_record/domain/model/randomdefense_record}/RandomDetail.java (68%) rename src/main/java/kr/co/morandi/backend/{domain/record/randomrecord/model => defense_record/domain/model/randomdefense_record}/RandomRecord.java (75%) rename src/main/java/kr/co/morandi/backend/{domain/detail => defense_record/domain/model/record}/Detail.java (78%) rename src/main/java/kr/co/morandi/backend/{domain => defense_record/domain/model}/record/Record.java (81%) rename src/main/java/kr/co/morandi/backend/{domain/detail/submit/model => defense_record/domain/model/record}/SubmitCode.java (80%) rename src/main/java/kr/co/morandi/backend/{domain/detail/stagedefense/model => defense_record/domain/model/stagedefense_record}/StageDetail.java (66%) rename src/main/java/kr/co/morandi/backend/{domain/record/stagerecord/model => defense_record/domain/model/stagedefense_record}/StageRecord.java (78%) rename src/main/java/kr/co/morandi/backend/{infrastructure/adapter/defense/record/dailyrecord => defense_record/infrastructure/adapter/dailydefense}/DailyRecordAdapter.java (64%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/customdefense_record/CustomDefenseRecordRepository.java rename src/main/java/kr/co/morandi/backend/{infrastructure/persistence/record/dailyrecord => defense_record/infrastructure/persistence/dailydefense_record}/DailyRecordRepository.java (79%) delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java delete mode 100644 src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/bookmark/BookMarkRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseProblemRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/random/RandomDefenseRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensememberlikes/ContentMemberLikesRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/problemalgorithm/ProblemAlgorithmRepository.java delete mode 100644 src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java rename src/main/java/kr/co/morandi/backend/{domain/member/model => member_management/domain/model/member}/Member.java (91%) rename src/main/java/kr/co/morandi/backend/{domain/member/model => member_management/domain/model/member}/SocialType.java (76%) rename src/main/java/kr/co/morandi/backend/{ => member_management}/infrastructure/persistence/member/MemberRepository.java (54%) rename src/main/java/kr/co/morandi/backend/{domain/algorithm/model => problem_information/domain/model/algorithm}/Algorithm.java (84%) rename src/main/java/kr/co/morandi/backend/{domain/problem/model => problem_information/domain/model/problem}/Problem.java (79%) rename src/main/java/kr/co/morandi/backend/{domain/problemalgorithm/model => problem_information/domain/model/problem}/ProblemAlgorithm.java (71%) rename src/main/java/kr/co/morandi/backend/{domain/problem/model => problem_information/domain/model/problem}/ProblemStatus.java (90%) rename src/main/java/kr/co/morandi/backend/{infrastructure/config => problem_information/infrastructure/initializer}/AlgorithmInitializer.java (85%) rename src/main/java/kr/co/morandi/backend/{ => problem_information}/infrastructure/persistence/algorithm/AlgorithmRepository.java (57%) create mode 100644 src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemAlgorithmRepository.java rename src/main/java/kr/co/morandi/backend/{ => problem_information}/infrastructure/persistence/problem/ProblemRepository.java (71%) rename src/test/java/kr/co/morandi/backend/{domain/defense => defense_information/domain/model}/customdefense/CustomDefenseTest.java (90%) rename src/test/java/kr/co/morandi/backend/{domain/detail => defense_information/domain/model}/customdefense/CustomDetailTest.java (71%) rename src/test/java/kr/co/morandi/backend/{domain/defense => defense_information/domain/model}/dailydefense/DailyDefenseProblemTest.java (86%) rename src/test/java/kr/co/morandi/backend/{domain/defense => defense_information/domain/model}/dailydefense/DailyDefenseTest.java (91%) rename src/test/java/kr/co/morandi/backend/{domain/detail => defense_information/domain/model}/dailydefense/DailyDetailTest.java (85%) rename src/test/java/kr/co/morandi/backend/{domain/defense/randomcriteria => defense_information/domain/model/defense}/DifficultyRangeTest.java (81%) rename src/test/java/kr/co/morandi/backend/{domain/defense/randomcriteria => defense_information/domain/model/defense}/RandomCriteriaTest.java (90%) rename src/test/java/kr/co/morandi/backend/{domain/defense => defense_information/domain/model}/randomdefense/RandomDefenseTest.java (88%) rename src/test/java/kr/co/morandi/backend/{domain/detail => defense_information/domain/model}/randomdefense/RandomDetailTest.java (84%) rename src/test/java/kr/co/morandi/backend/{domain/defense => defense_information/domain/model}/stagedefense/StageDefenseTest.java (89%) rename src/test/java/kr/co/morandi/backend/{domain/detail/randomdefense => defense_information/domain/model/stagedefense}/StageDetailTest.java (85%) rename src/test/java/kr/co/morandi/backend/{domain/defense/dailydefense/service => defense_information/domain/service/dailydefense}/DailyDefenseGenerationServiceTest.java (73%) rename src/test/java/kr/co/morandi/backend/{domain/defense/service => defense_information/domain/service/defense}/ProblemGenerationServiceTest.java (77%) rename src/test/java/kr/co/morandi/backend/{infrastructure/adapter/defense/defense => defense_information/infrastructure/adapter}/dailydefense/DailyDefenseProblemAdapterTest.java (83%) rename src/test/java/kr/co/morandi/backend/{ => defense_information}/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java (84%) rename src/test/java/kr/co/morandi/backend/{ => defense_information}/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java (69%) rename src/test/java/kr/co/morandi/backend/{infrastructure/persistence/defense => defense_information/infrastructure/persistence}/dailydefense/DailyDefenseProblemRepositoryTest.java (75%) rename src/test/java/kr/co/morandi/backend/{infrastructure/persistence/defense => defense_information/infrastructure/persistence}/randomdefense/RandomDefenseRepositoryTest.java (88%) rename src/test/java/kr/co/morandi/backend/{application/port/out/defensemanagement/defensesession => defense_management/application/port/out/session}/DefenseSessionPortTest.java (72%) rename src/test/java/kr/co/morandi/backend/{domain/defensemanagement/management/service => defense_management/application/service/dailydefense}/DailyDefenseManagementServiceTest.java (82%) rename src/test/java/kr/co/morandi/backend/{domain/defensemanagement/session/model => defense_management/domain/model/session}/DefenseSessionTest.java (88%) rename src/test/java/kr/co/morandi/backend/{domain/defensemanagement/sessiondetail/model => defense_management/domain/model/session}/SessionDetailTest.java (89%) rename src/test/java/kr/co/morandi/backend/{infrastructure/adapter/defensesession => defense_management/infrastructure/adapter/session}/DefenseSessionAdapterTest.java (71%) rename src/test/java/kr/co/morandi/backend/{domain/record/customdefenserecord => defense_record/domain/model/customdefense_record}/CustomRecordTest.java (88%) rename src/test/java/kr/co/morandi/backend/{domain/record/dailyrecord => defense_record/domain/model/dailydefense_record}/DailyRecordTest.java (92%) rename src/test/java/kr/co/morandi/backend/{domain/record/randomdefense => defense_record/domain/model/randomdefense_record}/RandomRecordTest.java (91%) rename src/test/java/kr/co/morandi/backend/{domain/record/stagerecord => defense_record/domain/model/stagedefense_record}/StageRecordTest.java (88%) rename src/test/java/kr/co/morandi/backend/{infrastructure/adapter/defense/record/dailyrecord => defense_record/infrastructure/adapter/dailydefense_record}/DailyRecordAdapterTest.java (76%) rename src/test/java/kr/co/morandi/backend/{infrastructure/persistence/record/dailyrecord => defense_record/infrastructure/persistence/dailydefense_record}/DailyRecordRepositoryTest.java (80%) delete mode 100644 src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java rename src/test/java/kr/co/morandi/backend/{domain => member_management/domain/model}/member/MemberTest.java (72%) rename src/test/java/kr/co/morandi/backend/{ => member_management}/infrastructure/persistence/member/MemberRepositoryTest.java (85%) rename src/test/java/kr/co/morandi/backend/{domain => problem_information/domain/model}/problem/ProblemTest.java (72%) rename src/test/java/kr/co/morandi/backend/{ => problem_information}/infrastructure/adapter/problem/ProblemAdapterTest.java (57%) rename src/test/java/kr/co/morandi/backend/{ => problem_information}/infrastructure/config/AlgorithmInitializerTest.java (84%) rename src/test/java/kr/co/morandi/backend/{ => problem_information}/infrastructure/persistence/problem/ProblemRepositoryTest.java (82%) diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java b/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java deleted file mode 100644 index c6c580d0..00000000 --- a/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefensePort.java +++ /dev/null @@ -1,12 +0,0 @@ -package kr.co.morandi.backend.application.port.out.defense.dailydefense; - -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; - -import java.time.LocalDate; - -public interface DailyDefensePort { - DailyDefense findDailyDefense(DefenseType defenseType, LocalDate date); - - DailyDefense saveDailyDefense(DailyDefense dailyDefense); -} diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java b/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java deleted file mode 100644 index 8f6cac87..00000000 --- a/src/main/java/kr/co/morandi/backend/application/port/out/defense/dailydefense/DailyDefenseProblemPort.java +++ /dev/null @@ -1,11 +0,0 @@ -package kr.co.morandi.backend.application.port.out.defense.dailydefense; - -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.problem.model.Problem; - -import java.util.Map; - -public interface DailyDefenseProblemPort { - - Map getDailyDefenseProblem(Map criteria); -} diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java b/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java deleted file mode 100644 index ed0c10c9..00000000 --- a/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/sessiondetail/SessionDetailPort.java +++ /dev/null @@ -1,10 +0,0 @@ -package kr.co.morandi.backend.application.port.out.defensemanagement.sessiondetail; - -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; - -import java.util.Optional; - -public interface SessionDetailPort { - Optional findSessionDetail(DefenseSession defenseSession, Long problemId); -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/config/JpaAuditingConfig.java b/src/main/java/kr/co/morandi/backend/common/config/JpaAuditingConfig.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/infrastructure/config/JpaAuditingConfig.java rename to src/main/java/kr/co/morandi/backend/common/config/JpaAuditingConfig.java index e106c29a..caebfccb 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/config/JpaAuditingConfig.java +++ b/src/main/java/kr/co/morandi/backend/common/config/JpaAuditingConfig.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.infrastructure.config; +package kr.co.morandi.backend.common.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/kr/co/morandi/backend/domain/BaseEntity.java b/src/main/java/kr/co/morandi/backend/common/model/BaseEntity.java similarity index 94% rename from src/main/java/kr/co/morandi/backend/domain/BaseEntity.java rename to src/main/java/kr/co/morandi/backend/common/model/BaseEntity.java index 9b9ee285..dcf52ab7 100644 --- a/src/main/java/kr/co/morandi/backend/domain/BaseEntity.java +++ b/src/main/java/kr/co/morandi/backend/common/model/BaseEntity.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain; +package kr.co.morandi.backend.common.model; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefensePort.java b/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefensePort.java new file mode 100644 index 00000000..610daafe --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefensePort.java @@ -0,0 +1,12 @@ +package kr.co.morandi.backend.defense_information.application.port.out.dailydefense; + +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; + +import java.time.LocalDate; + +public interface DailyDefensePort { + DailyDefense findDailyDefense(DefenseType defenseType, LocalDate date); + + DailyDefense saveDailyDefense(DailyDefense dailyDefense); +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java b/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java new file mode 100644 index 00000000..bc14f190 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.defense_information.application.port.out.dailydefense; + +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; + +import java.util.Map; + +public interface DailyDefenseProblemPort { + + Map getDailyDefenseProblem(Map criteria); +} diff --git a/src/main/java/kr/co/morandi/backend/domain/bookmark/model/BookMark.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/BookMark.java similarity index 63% rename from src/main/java/kr/co/morandi/backend/domain/bookmark/model/BookMark.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/BookMark.java index a29201c3..b4877c60 100644 --- a/src/main/java/kr/co/morandi/backend/domain/bookmark/model/BookMark.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/BookMark.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.bookmark.model; +package kr.co.morandi.backend.defense_information.domain.model.customdefense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import lombok.*; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/model/ContentMemberLikes.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/ContentMemberLikes.java similarity index 68% rename from src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/model/ContentMemberLikes.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/ContentMemberLikes.java index 4bfc0712..a387882d 100644 --- a/src/main/java/kr/co/morandi/backend/domain/contentmemberlikes/model/ContentMemberLikes.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/ContentMemberLikes.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.contentmemberlikes.model; +package kr.co.morandi.backend.defense_information.domain.model.customdefense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import lombok.AccessLevel; import lombok.Builder; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefense.java similarity index 85% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefense.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefense.java index 2851af1b..b2880170 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefense.java @@ -1,9 +1,10 @@ -package kr.co.morandi.backend.domain.defense.customdefense.model; +package kr.co.morandi.backend.defense_information.domain.model.customdefense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseTier; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -15,7 +16,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import static kr.co.morandi.backend.domain.defense.DefenseType.CUSTOM; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.CUSTOM; @Entity @DiscriminatorValue("CustomDefense") diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefenseProblem.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefenseProblem.java similarity index 86% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefenseProblem.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefenseProblem.java index 23f07a16..fe7d6e2f 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/CustomDefenseProblem.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefenseProblem.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defense.customdefense.model; +package kr.co.morandi.backend.defense_information.domain.model.customdefense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.*; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/Visibility.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/Visibility.java similarity index 71% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/Visibility.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/Visibility.java index 17a5c2f0..9ea30ffe 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/Visibility.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/Visibility.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.defense.customdefense.model; +package kr.co.morandi.backend.defense_information.domain.model.customdefense; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefense.java similarity index 83% rename from src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefense.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefense.java index c44cabfa..99436c59 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefense.java @@ -1,12 +1,12 @@ -package kr.co.morandi.backend.domain.defense.dailydefense.model; +package kr.co.morandi.backend.defense_information.domain.model.dailydefense; import jakarta.persistence.CascadeType; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.OneToMany; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -20,7 +20,7 @@ import java.util.Map; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; @Entity @DiscriminatorValue("DailyDefense") diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefenseProblem.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseProblem.java similarity index 85% rename from src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefenseProblem.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseProblem.java index 7b230972..8804b1d7 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/model/DailyDefenseProblem.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseProblem.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defense.dailydefense.model; +package kr.co.morandi.backend.defense_information.domain.model.dailydefense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/domain/defense/Defense.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java index 601b597c..d0e170fb 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/Defense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.defense; +package kr.co.morandi.backend.defense_information.domain.model.defense; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/DefenseTier.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/DefenseTier.java similarity index 76% rename from src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/DefenseTier.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/DefenseTier.java index 0165715a..3b4f6c8a 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/model/DefenseTier.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/DefenseTier.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.defense.customdefense.model; +package kr.co.morandi.backend.defense_information.domain.model.defense; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/DefenseType.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/DefenseType.java new file mode 100644 index 00000000..73778505 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/DefenseType.java @@ -0,0 +1,6 @@ +package kr.co.morandi.backend.defense_information.domain.model.defense; + +public enum DefenseType { + DAILY, CUSTOM, STAGE, RANDOM + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/tier/model/ProblemTier.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/ProblemTier.java similarity index 90% rename from src/main/java/kr/co/morandi/backend/domain/defense/tier/model/ProblemTier.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/ProblemTier.java index 2b9cdf91..44d5db17 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/tier/model/ProblemTier.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/ProblemTier.java @@ -1,11 +1,10 @@ -package kr.co.morandi.backend.domain.defense.tier.model; +package kr.co.morandi.backend.defense_information.domain.model.defense; import lombok.Getter; import lombok.RequiredArgsConstructor; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; @Getter @RequiredArgsConstructor diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/random/model/randomcriteria/RandomCriteria.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/RandomCriteria.java similarity index 94% rename from src/main/java/kr/co/morandi/backend/domain/defense/random/model/randomcriteria/RandomCriteria.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/RandomCriteria.java index a526338b..eec66780 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/random/model/randomcriteria/RandomCriteria.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/RandomCriteria.java @@ -1,8 +1,7 @@ -package kr.co.morandi.backend.domain.defense.random.model.randomcriteria; +package kr.co.morandi.backend.defense_information.domain.model.defense; import jakarta.persistence.Embeddable; import jakarta.persistence.Embedded; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/problem_generation_strategy/ProblemGenerationStrategy.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/problem_generation_strategy/ProblemGenerationStrategy.java new file mode 100644 index 00000000..bc96682b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/problem_generation_strategy/ProblemGenerationStrategy.java @@ -0,0 +1,14 @@ +package kr.co.morandi.backend.defense_information.domain.model.problem_generation_strategy; + +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; + +import java.util.Map; + +public interface ProblemGenerationStrategy { + + Map generateDefenseProblems(Defense defense); + DefenseType getDefenseType(); + +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/random/model/RandomDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/model/RandomDefense.java similarity index 82% rename from src/main/java/kr/co/morandi/backend/domain/defense/random/model/RandomDefense.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/model/RandomDefense.java index e6b07439..336f4844 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/random/model/RandomDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/model/RandomDefense.java @@ -1,14 +1,14 @@ -package kr.co.morandi.backend.domain.defense.random.model; +package kr.co.morandi.backend.defense_information.domain.model.randomdefense.model; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import lombok.*; import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; -import static kr.co.morandi.backend.domain.defense.DefenseType.RANDOM; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.RANDOM; @Entity @DiscriminatorValue("RandomDefense") diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/model/StageDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/model/StageDefense.java similarity index 78% rename from src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/model/StageDefense.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/model/StageDefense.java index caf87330..9a58bc4f 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/stagedefense/model/StageDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/model/StageDefense.java @@ -1,14 +1,14 @@ -package kr.co.morandi.backend.domain.defense.stagedefense.model; +package kr.co.morandi.backend.defense_information.domain.model.stagedefense.model; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; import lombok.*; import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; -import static kr.co.morandi.backend.domain.defense.DefenseType.STAGE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.STAGE; @Entity @DiscriminatorValue("StageDefense") diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/service/customdefense/CustomDefenseStrategy.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/customdefense/CustomDefenseStrategy.java new file mode 100644 index 00000000..8e65ad3b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/customdefense/CustomDefenseStrategy.java @@ -0,0 +1,24 @@ +package kr.co.morandi.backend.defense_information.domain.service.customdefense; + +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_information.domain.model.problem_generation_strategy.ProblemGenerationStrategy; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.CUSTOM; + +@Component +public class CustomDefenseStrategy implements ProblemGenerationStrategy { + + @Override + public Map generateDefenseProblems(Defense defense) { + return null; + } + @Override + public DefenseType getDefenseType() { + return CUSTOM; + } +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationService.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationService.java similarity index 77% rename from src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationService.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationService.java index cddfe8c8..da3ad8d5 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationService.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationService.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.defense.dailydefense.service; - -import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; -import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefenseProblemPort; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; -import kr.co.morandi.backend.domain.problem.model.Problem; +package kr.co.morandi.backend.defense_information.domain.service.dailydefense; + +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefenseProblemPort; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,7 +14,7 @@ import java.time.LocalDateTime; import java.util.Map; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; @Service @RequiredArgsConstructor diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseStrategy.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseStrategy.java similarity index 54% rename from src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseStrategy.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseStrategy.java index 7c33926f..b366f089 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseStrategy.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseStrategy.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.defense.dailydefense.service; +package kr.co.morandi.backend.defense_information.domain.service.dailydefense; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.ProblemGenerationStrategy; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_information.domain.model.problem_generation_strategy.ProblemGenerationStrategy; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -13,7 +13,7 @@ import java.util.Map; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; @Component @RequiredArgsConstructor diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/service/ProblemGenerationService.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationService.java similarity index 60% rename from src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/service/ProblemGenerationService.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationService.java index c4f5c3ef..9500a5ab 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/service/ProblemGenerationService.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationService.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service; +package kr.co.morandi.backend.defense_information.domain.service.defense; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.ProblemGenerationStrategy; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_information.domain.model.problem_generation_strategy.ProblemGenerationStrategy; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import org.springframework.stereotype.Component; import java.util.List; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseAdapter.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseAdapter.java similarity index 60% rename from src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseAdapter.java rename to src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseAdapter.java index 4aa5b4e7..60c175f1 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseAdapter.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.infrastructure.adapter.defense.defense.dailydefense; +package kr.co.morandi.backend.defense_information.infrastructure.adapter.dailydefense; -import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapter.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapter.java similarity index 75% rename from src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapter.java rename to src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapter.java index 07deeea7..be2b09ae 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapter.java @@ -1,10 +1,10 @@ -package kr.co.morandi.backend.infrastructure.adapter.defense.defense.dailydefense; +package kr.co.morandi.backend.defense_information.infrastructure.adapter.dailydefense; -import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefenseProblemPort; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefenseProblemPort; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/config/SchedulingConfig.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/config/defense/SchedulingConfig.java similarity index 70% rename from src/main/java/kr/co/morandi/backend/infrastructure/config/SchedulingConfig.java rename to src/main/java/kr/co/morandi/backend/defense_information/infrastructure/config/defense/SchedulingConfig.java index baf05f14..9f6a4f9c 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/config/SchedulingConfig.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/config/defense/SchedulingConfig.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.infrastructure.config; +package kr.co.morandi.backend.defense_information.infrastructure.config.defense; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/BookMarkRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/BookMarkRepository.java new file mode 100644 index 00000000..8260cbc4 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/BookMarkRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense; + +import kr.co.morandi.backend.defense_information.domain.model.customdefense.BookMark; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BookMarkRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/ContentMemberLikesRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/ContentMemberLikesRepository.java new file mode 100644 index 00000000..80b5553f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/ContentMemberLikesRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense; + +import kr.co.morandi.backend.defense_information.domain.model.customdefense.ContentMemberLikes; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ContentMemberLikesRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseProblemRepository.java new file mode 100644 index 00000000..c6a28664 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseProblemRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense; + +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefenseProblem; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDefenseProblemRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepository.java new file mode 100644 index 00000000..b163574c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepository.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense; + +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; +import kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CustomDefenseRepository extends JpaRepository { + List findAllByVisibility(Visibility visibility); +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepository.java similarity index 72% rename from src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepository.java rename to src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepository.java index 25426831..732b8905 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepository.java @@ -1,6 +1,6 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense; +package kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseRepository.java similarity index 67% rename from src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseRepository.java rename to src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseRepository.java index 390159e3..9342bfae 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseRepository.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense; +package kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDetailRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDetailRepository.java new file mode 100644 index 00000000..9bf62f6e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDetailRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense; + +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DailyDetailRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/defense/DefenseRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/defense/DefenseRepository.java new file mode 100644 index 00000000..b309795c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/defense/DefenseRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.defense; + +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepository.java new file mode 100644 index 00000000..19f003c7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.randomdefense; + +import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RandomDefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/stagedefense/StageDefenseRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/stagedefense/StageDefenseRepository.java new file mode 100644 index 00000000..6aa51cda --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/stagedefense/StageDefenseRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_information.infrastructure.persistence.stagedefense; + +import kr.co.morandi.backend.defense_information.domain.model.stagedefense.model.StageDefense; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StageDefenseRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java similarity index 80% rename from src/main/java/kr/co/morandi/backend/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java rename to src/main/java/kr/co/morandi/backend/defense_information/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java index 79ac3641..d23daea9 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/scheduler/dailydefense/DailyDefenseGenerationScheduler.java @@ -1,6 +1,6 @@ -package kr.co.morandi.backend.infrastructure.scheduler.dailydefense; +package kr.co.morandi.backend.defense_information.infrastructure.scheduler.dailydefense; -import kr.co.morandi.backend.domain.defense.dailydefense.service.DailyDefenseGenerationService; +import kr.co.morandi.backend.defense_information.domain.service.dailydefense.DailyDefenseGenerationService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPort.java b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java similarity index 52% rename from src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPort.java rename to src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java index e2218137..949863b7 100644 --- a/src/main/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPort.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.application.port.out.defensemanagement.defensesession; +package kr.co.morandi.backend.defense_management.application.port.out.session; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import java.time.LocalDateTime; import java.util.Optional; diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/SessionDetailPort.java b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/SessionDetailPort.java new file mode 100644 index 00000000..7ced5a6a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/SessionDetailPort.java @@ -0,0 +1,10 @@ +package kr.co.morandi.backend.defense_management.application.port.out.session; + +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; + +import java.util.Optional; + +public interface SessionDetailPort { + Optional findSessionDetail(DefenseSession defenseSession, Long problemId); +} diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/request/StartDailyDefenseServiceRequest.java b/src/main/java/kr/co/morandi/backend/defense_management/application/request/session/StartDailyDefenseServiceRequest.java similarity index 85% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/request/StartDailyDefenseServiceRequest.java rename to src/main/java/kr/co/morandi/backend/defense_management/application/request/session/StartDailyDefenseServiceRequest.java index b9c89c33..e58d875a 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/request/StartDailyDefenseServiceRequest.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/request/session/StartDailyDefenseServiceRequest.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.defensemanagement.management.request; +package kr.co.morandi.backend.defense_management.application.request.session; import lombok.AccessLevel; import lombok.Builder; diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/DefenseProblemResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/DefenseProblemResponse.java similarity index 78% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/DefenseProblemResponse.java rename to src/main/java/kr/co/morandi/backend/defense_management/application/response/session/DefenseProblemResponse.java index 4b063a7d..5c2cb65d 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/DefenseProblemResponse.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/DefenseProblemResponse.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.defensemanagement.management.response; - -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; -import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language; -import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +package kr.co.morandi.backend.defense_management.application.response.session; + +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/StartDailyDefenseServiceResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseServiceResponse.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/StartDailyDefenseServiceResponse.java rename to src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseServiceResponse.java index 14ff0e17..a12500d3 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/response/StartDailyDefenseServiceResponse.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseServiceResponse.java @@ -1,10 +1,10 @@ -package kr.co.morandi.backend.domain.defensemanagement.management.response; +package kr.co.morandi.backend.defense_management.application.response.session; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java similarity index 72% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementService.java rename to src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java index 6fd929e3..cf68f643 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementService.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java @@ -1,16 +1,16 @@ -package kr.co.morandi.backend.domain.defensemanagement.management.service; - -import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; -import kr.co.morandi.backend.application.port.out.defensemanagement.defensesession.DefenseSessionPort; -import kr.co.morandi.backend.application.port.out.record.dailyrecord.DailyRecordPort; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; -import kr.co.morandi.backend.domain.defensemanagement.management.request.StartDailyDefenseServiceRequest; -import kr.co.morandi.backend.domain.defensemanagement.management.response.StartDailyDefenseServiceResponse; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +package kr.co.morandi.backend.defense_management.application.service.session; + +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; +import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseServiceResponse; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,7 +19,7 @@ import java.util.Map; import java.util.Optional; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; @Service @Transactional(readOnly = true) diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSession.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java similarity index 84% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSession.java rename to src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java index ff1e46e3..94b069e0 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSession.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java @@ -1,10 +1,9 @@ -package kr.co.morandi.backend.domain.defensemanagement.session.model; +package kr.co.morandi.backend.defense_management.domain.model.session; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; -import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,9 +15,6 @@ import java.util.Set; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defensemanagement.session.model.ExamStatus.COMPLETED; -import static kr.co.morandi.backend.domain.defensemanagement.session.model.ExamStatus.IN_PROGRESS; - @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -62,7 +58,7 @@ public boolean hasTriedProblem(Long problemNumber) { .anyMatch(detail -> Objects.equals(detail.getProblemNumber(), problemNumber)); } public void tryMoreProblem(Long problemNumber, LocalDateTime accessDateTime) { - if (examStatus == COMPLETED || accessDateTime.isAfter(endDateTime)) { + if (examStatus == ExamStatus.COMPLETED || accessDateTime.isAfter(endDateTime)) { throw new IllegalStateException("이미 종료된 시험입니다."); } // 이미 있는 시험이라면 @@ -91,7 +87,7 @@ private DefenseSession(Member member, Long recordId, DefenseType defenseType, Se .collect(Collectors.toCollection(ArrayList::new)); this.startDateTime = startDateTime; this.endDateTime = endDateTime; - this.examStatus = IN_PROGRESS; + this.examStatus = ExamStatus.IN_PROGRESS; this.lastAccessDateTime = startDateTime; this.lastAccessProblemNumber = problemNumbers.stream() .findFirst() diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/ExamStatus.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/ExamStatus.java similarity index 50% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/ExamStatus.java rename to src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/ExamStatus.java index 382d844b..6890a3a0 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/session/model/ExamStatus.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/ExamStatus.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.defensemanagement.session.model; +package kr.co.morandi.backend.defense_management.domain.model.session; public enum ExamStatus { IN_PROGRESS, COMPLETED, STAGE_PROGRESS; diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetail.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java similarity index 88% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetail.java rename to src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java index e56d7ca3..74e7351d 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java @@ -1,10 +1,9 @@ -package kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model; +package kr.co.morandi.backend.defense_management.domain.model.session; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language; -import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/Language.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/Language.java similarity index 88% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/Language.java rename to src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/Language.java index 2ab46983..afbc970b 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/Language.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/Language.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.defensemanagement.tempcode.model; +package kr.co.morandi.backend.defense_management.domain.model.tempcode.model; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/TempCode.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/TempCode.java similarity index 87% rename from src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/TempCode.java rename to src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/TempCode.java index 98de3fb7..0adacd11 100644 --- a/src/main/java/kr/co/morandi/backend/domain/defensemanagement/tempcode/model/TempCode.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/TempCode.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defensemanagement.tempcode.model; +package kr.co.morandi.backend.defense_management.domain.model.tempcode.model; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapter.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java similarity index 55% rename from src/main/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapter.java rename to src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java index d9a2fc91..ae98400f 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java @@ -1,16 +1,16 @@ -package kr.co.morandi.backend.infrastructure.adapter.defensesession; +package kr.co.morandi.backend.defense_management.infrastructure.adapter.session; -import kr.co.morandi.backend.application.port.out.defensemanagement.defensesession.DefenseSessionPort; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.Optional; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; @Service @RequiredArgsConstructor diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/session/DefenseSessionRepository.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepository.java similarity index 65% rename from src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/session/DefenseSessionRepository.java rename to src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepository.java index f247efd9..4792e571 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/session/DefenseSessionRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepository.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session; +package kr.co.morandi.backend.defense_management.infrastructure.persistence.session; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/SessionDetailRepository.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/SessionDetailRepository.java new file mode 100644 index 00000000..ac9d9c12 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/SessionDetailRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_management.infrastructure.persistence.session; + +import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SessionDetailRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/tempcode/TempCodeRepository.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/tempcode/TempCodeRepository.java new file mode 100644 index 00000000..f873ddec --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/tempcode/TempCodeRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_management.infrastructure.persistence.tempcode; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TempCodeRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/web/defensemanagement/dailydefense/dto/request/StartDailyDefenseRequest.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/dailydefense/StartDailyDefenseRequest.java similarity index 74% rename from src/main/java/kr/co/morandi/backend/web/defensemanagement/dailydefense/dto/request/StartDailyDefenseRequest.java rename to src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/dailydefense/StartDailyDefenseRequest.java index 12c789dd..0ce7b738 100644 --- a/src/main/java/kr/co/morandi/backend/web/defensemanagement/dailydefense/dto/request/StartDailyDefenseRequest.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/dailydefense/StartDailyDefenseRequest.java @@ -1,6 +1,6 @@ -package kr.co.morandi.backend.web.defensemanagement.dailydefense.dto.request; +package kr.co.morandi.backend.defense_management.infrastructure.request.dailydefense; -import kr.co.morandi.backend.domain.defensemanagement.management.request.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/application/port/out/record/dailyrecord/DailyRecordPort.java b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/dailyrecord/DailyRecordPort.java similarity index 56% rename from src/main/java/kr/co/morandi/backend/application/port/out/record/dailyrecord/DailyRecordPort.java rename to src/main/java/kr/co/morandi/backend/defense_record/application/port/out/dailyrecord/DailyRecordPort.java index f8bb7bf9..46f3d845 100644 --- a/src/main/java/kr/co/morandi/backend/application/port/out/record/dailyrecord/DailyRecordPort.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/dailyrecord/DailyRecordPort.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.application.port.out.record.dailyrecord; +package kr.co.morandi.backend.defense_record.application.port.out.dailyrecord; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import java.time.LocalDate; import java.util.Optional; diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/model/CustomDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomDetail.java similarity index 68% rename from src/main/java/kr/co/morandi/backend/domain/detail/customdefense/model/CustomDetail.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomDetail.java index ccaeb16b..ed123f9a 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/customdefense/model/CustomDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomDetail.java @@ -1,12 +1,12 @@ -package kr.co.morandi.backend.domain.detail.customdefense.model; +package kr.co.morandi.backend.defense_record.domain.model.customdefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/record/customdefenserecord/model/CustomRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java similarity index 73% rename from src/main/java/kr/co/morandi/backend/domain/record/customdefenserecord/model/CustomRecord.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java index e34f483c..03797d50 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/customdefenserecord/model/CustomRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java @@ -1,13 +1,12 @@ -package kr.co.morandi.backend.domain.record.customdefenserecord.model; +package kr.co.morandi.backend.defense_record.domain.model.customdefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; -import kr.co.morandi.backend.domain.detail.customdefense.model.CustomDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/model/DailyDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java similarity index 64% rename from src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/model/DailyDetail.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java index f85c3551..418cc5de 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/dailydefense/model/DailyDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java @@ -1,12 +1,12 @@ -package kr.co.morandi.backend.domain.detail.dailydefense.model; +package kr.co.morandi.backend.defense_record.domain.model.dailydefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/record/dailyrecord/model/DailyRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java similarity index 82% rename from src/main/java/kr/co/morandi/backend/domain/record/dailyrecord/model/DailyRecord.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java index 70b8e93a..1bbb9b3f 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/dailyrecord/model/DailyRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java @@ -1,14 +1,13 @@ -package kr.co.morandi.backend.domain.record.dailyrecord.model; +package kr.co.morandi.backend.defense_record.domain.model.dailydefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.detail.dailydefense.model.DailyDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/model/RandomDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomDetail.java similarity index 68% rename from src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/model/RandomDetail.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomDetail.java index b8e66ad6..57f2c6d5 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/randomdefense/model/RandomDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomDetail.java @@ -1,12 +1,12 @@ -package kr.co.morandi.backend.domain.detail.randomdefense.model; +package kr.co.morandi.backend.defense_record.domain.model.randomdefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/record/randomrecord/model/RandomRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecord.java similarity index 75% rename from src/main/java/kr/co/morandi/backend/domain/record/randomrecord/model/RandomRecord.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecord.java index 2da47046..4bab08bd 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/randomrecord/model/RandomRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecord.java @@ -1,13 +1,12 @@ -package kr.co.morandi.backend.domain.record.randomrecord.model; +package kr.co.morandi.backend.defense_record.domain.model.randomdefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; -import kr.co.morandi.backend.domain.detail.randomdefense.model.RandomDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java similarity index 78% rename from src/main/java/kr/co/morandi/backend/domain/detail/Detail.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java index 26426ca5..b004f524 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/Detail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java @@ -1,11 +1,10 @@ -package kr.co.morandi.backend.domain.detail; +package kr.co.morandi.backend.defense_record.domain.model.record; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/record/Record.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java similarity index 81% rename from src/main/java/kr/co/morandi/backend/domain/record/Record.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java index 8fd957e3..4a0f94f9 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/Record.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java @@ -1,11 +1,10 @@ -package kr.co.morandi.backend.domain.record; +package kr.co.morandi.backend.defense_record.domain.model.record; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/submit/model/SubmitCode.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/SubmitCode.java similarity index 80% rename from src/main/java/kr/co/morandi/backend/domain/detail/submit/model/SubmitCode.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/SubmitCode.java index de2ccb61..eb09b084 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/submit/model/SubmitCode.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/SubmitCode.java @@ -1,8 +1,7 @@ -package kr.co.morandi.backend.domain.detail.submit.model; +package kr.co.morandi.backend.defense_record.domain.model.record; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/model/StageDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java similarity index 66% rename from src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/model/StageDetail.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java index a6c97bfe..3b1d3802 100644 --- a/src/main/java/kr/co/morandi/backend/domain/detail/stagedefense/model/StageDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java @@ -1,12 +1,12 @@ -package kr.co.morandi.backend.domain.detail.stagedefense.model; +package kr.co.morandi.backend.defense_record.domain.model.stagedefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.detail.Detail; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/domain/record/stagerecord/model/StageRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java similarity index 78% rename from src/main/java/kr/co/morandi/backend/domain/record/stagerecord/model/StageRecord.java rename to src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java index 13984ebf..d173dd76 100644 --- a/src/main/java/kr/co/morandi/backend/domain/record/stagerecord/model/StageRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java @@ -1,12 +1,11 @@ -package kr.co.morandi.backend.domain.record.stagerecord.model; +package kr.co.morandi.backend.defense_record.domain.model.stagedefense_record; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.detail.stagedefense.model.StageDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.Record; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapter.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense/DailyRecordAdapter.java similarity index 64% rename from src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapter.java rename to src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense/DailyRecordAdapter.java index daa71dae..13180100 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense/DailyRecordAdapter.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.infrastructure.adapter.defense.record.dailyrecord; +package kr.co.morandi.backend.defense_record.infrastructure.adapter.dailydefense; -import kr.co.morandi.backend.application.port.out.record.dailyrecord.DailyRecordPort; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; -import kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord.DailyRecordRepository; +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/customdefense_record/CustomDefenseRecordRepository.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/customdefense_record/CustomDefenseRecordRepository.java new file mode 100644 index 00000000..919464e2 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/customdefense_record/CustomDefenseRecordRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_record.infrastructure.persistence.customdefense_record; + +import kr.co.morandi.backend.defense_record.domain.model.customdefense_record.CustomRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomDefenseRecordRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepository.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepository.java rename to src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java index ca9dc652..24c5c4ea 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord; +package kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java b/src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java deleted file mode 100644 index fd28f244..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/DefenseType.java +++ /dev/null @@ -1,6 +0,0 @@ -package kr.co.morandi.backend.domain.defense; - -public enum DefenseType { - DAILY, CUSTOM, STAGE, RANDOM - -} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java b/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java deleted file mode 100644 index 17a1fdad..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/customdefense/service/CustomDefenseStrategy.java +++ /dev/null @@ -1,24 +0,0 @@ -package kr.co.morandi.backend.domain.defense.customdefense.service; - -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.ProblemGenerationStrategy; -import kr.co.morandi.backend.domain.problem.model.Problem; -import org.springframework.stereotype.Component; - -import java.util.Map; - -import static kr.co.morandi.backend.domain.defense.DefenseType.CUSTOM; - -@Component -public class CustomDefenseStrategy implements ProblemGenerationStrategy { - - @Override - public Map generateDefenseProblems(Defense defense) { - return null; - } - @Override - public DefenseType getDefenseType() { - return CUSTOM; - } -} diff --git a/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java b/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java deleted file mode 100644 index 846cdc03..00000000 --- a/src/main/java/kr/co/morandi/backend/domain/defense/problemgenerationstrategy/ProblemGenerationStrategy.java +++ /dev/null @@ -1,14 +0,0 @@ -package kr.co.morandi.backend.domain.defense.problemgenerationstrategy; - -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.DefenseType; -import kr.co.morandi.backend.domain.problem.model.Problem; - -import java.util.Map; - -public interface ProblemGenerationStrategy { - - Map generateDefenseProblems(Defense defense); - DefenseType getDefenseType(); - -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/bookmark/BookMarkRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/bookmark/BookMarkRepository.java deleted file mode 100644 index 3993d02e..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/bookmark/BookMarkRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.bookmark; - -import kr.co.morandi.backend.domain.bookmark.model.BookMark; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface BookMarkRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java deleted file mode 100644 index 95b00313..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/DefenseRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense; - -import kr.co.morandi.backend.domain.defense.Defense; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DefenseRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseProblemRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseProblemRepository.java deleted file mode 100644 index b9230b6e..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseProblemRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.customdefense; - -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefenseProblem; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CustomDefenseProblemRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseRepository.java deleted file mode 100644 index 89ce599e..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/customdefense/CustomDefenseRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.customdefense; - -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; -import kr.co.morandi.backend.domain.defense.customdefense.model.Visibility; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface CustomDefenseRepository extends JpaRepository { - List findAllByVisibility(Visibility visibility); -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/random/RandomDefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/random/RandomDefenseRepository.java deleted file mode 100644 index 43ff6314..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/random/RandomDefenseRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.random; - -import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface RandomDefenseRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java deleted file mode 100644 index ffc59b5a..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defense/stagedefense/StageDefenseRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.stagedefense; - -import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface StageDefenseRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java deleted file mode 100644 index 9dac9ee3..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/sessiondetail/SessionDetailRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defensemanagement.sessiondetail; - -import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface SessionDetailRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java deleted file mode 100644 index 7f6ae6a8..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensemanagement/tempcode/TempCodeRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defensemanagement.tempcode; - -import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface TempCodeRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensememberlikes/ContentMemberLikesRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensememberlikes/ContentMemberLikesRepository.java deleted file mode 100644 index ae52e635..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/defensememberlikes/ContentMemberLikesRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.defensememberlikes; - -import kr.co.morandi.backend.domain.contentmemberlikes.model.ContentMemberLikes; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ContentMemberLikesRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problemalgorithm/ProblemAlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problemalgorithm/ProblemAlgorithmRepository.java deleted file mode 100644 index dcd57750..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problemalgorithm/ProblemAlgorithmRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.problemalgorithm; - -import kr.co.morandi.backend.domain.problemalgorithm.model.ProblemAlgorithm; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ProblemAlgorithmRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java b/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java deleted file mode 100644 index 6eb10b13..00000000 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/record/customrecord/CustomDefenseRecordRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.record.customrecord; - -import kr.co.morandi.backend.domain.record.customdefenserecord.model.CustomRecord; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CustomDefenseRecordRepository extends JpaRepository { -} diff --git a/src/main/java/kr/co/morandi/backend/domain/member/model/Member.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java similarity index 91% rename from src/main/java/kr/co/morandi/backend/domain/member/model/Member.java rename to src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java index 5bb971d3..4f7c569e 100644 --- a/src/main/java/kr/co/morandi/backend/domain/member/model/Member.java +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.domain.member.model; +package kr.co.morandi.backend.member_management.domain.model.member; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.common.model.BaseEntity; import lombok.*; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/member/model/SocialType.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/SocialType.java similarity index 76% rename from src/main/java/kr/co/morandi/backend/domain/member/model/SocialType.java rename to src/main/java/kr/co/morandi/backend/member_management/domain/model/member/SocialType.java index 20883cd6..13a36697 100644 --- a/src/main/java/kr/co/morandi/backend/domain/member/model/SocialType.java +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/SocialType.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.member.model; +package kr.co.morandi.backend.member_management.domain.model.member; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepository.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java similarity index 54% rename from src/main/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepository.java rename to src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java index 931bf849..d24ec1d4 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepository.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java @@ -1,6 +1,6 @@ -package kr.co.morandi.backend.infrastructure.persistence.member; +package kr.co.morandi.backend.member_management.infrastructure.persistence.member; -import kr.co.morandi.backend.domain.member.model.Member; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { diff --git a/src/main/java/kr/co/morandi/backend/domain/algorithm/model/Algorithm.java b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/algorithm/Algorithm.java similarity index 84% rename from src/main/java/kr/co/morandi/backend/domain/algorithm/model/Algorithm.java rename to src/main/java/kr/co/morandi/backend/problem_information/domain/model/algorithm/Algorithm.java index 28f5b0bf..fc082c96 100644 --- a/src/main/java/kr/co/morandi/backend/domain/algorithm/model/Algorithm.java +++ b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/algorithm/Algorithm.java @@ -1,10 +1,10 @@ -package kr.co.morandi.backend.domain.algorithm.model; +package kr.co.morandi.backend.problem_information.domain.model.algorithm; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import kr.co.morandi.backend.domain.BaseEntity; +import kr.co.morandi.backend.common.model.BaseEntity; import lombok.*; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/model/Problem.java b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/Problem.java similarity index 79% rename from src/main/java/kr/co/morandi/backend/domain/problem/model/Problem.java rename to src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/Problem.java index 4a1340fe..27c8d072 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problem/model/Problem.java +++ b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/Problem.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.problem.model; +package kr.co.morandi.backend.problem_information.domain.model.problem; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; import lombok.*; -import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.INIT; +import static kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus.INIT; @Entity @Getter diff --git a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/model/ProblemAlgorithm.java b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemAlgorithm.java similarity index 71% rename from src/main/java/kr/co/morandi/backend/domain/problemalgorithm/model/ProblemAlgorithm.java rename to src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemAlgorithm.java index 93e6274d..59b7194a 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problemalgorithm/model/ProblemAlgorithm.java +++ b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemAlgorithm.java @@ -1,9 +1,8 @@ -package kr.co.morandi.backend.domain.problemalgorithm.model; +package kr.co.morandi.backend.problem_information.domain.model.problem; import jakarta.persistence.*; -import kr.co.morandi.backend.domain.BaseEntity; -import kr.co.morandi.backend.domain.algorithm.model.Algorithm; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.problem_information.domain.model.algorithm.Algorithm; import lombok.*; @Entity diff --git a/src/main/java/kr/co/morandi/backend/domain/problem/model/ProblemStatus.java b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemStatus.java similarity index 90% rename from src/main/java/kr/co/morandi/backend/domain/problem/model/ProblemStatus.java rename to src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemStatus.java index e135f903..c1d33cb9 100644 --- a/src/main/java/kr/co/morandi/backend/domain/problem/model/ProblemStatus.java +++ b/src/main/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemStatus.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.domain.problem.model; +package kr.co.morandi.backend.problem_information.domain.model.problem; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializer.java b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/initializer/AlgorithmInitializer.java similarity index 85% rename from src/main/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializer.java rename to src/main/java/kr/co/morandi/backend/problem_information/infrastructure/initializer/AlgorithmInitializer.java index 09cb4f4d..b16c66d9 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializer.java +++ b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/initializer/AlgorithmInitializer.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.infrastructure.config; +package kr.co.morandi.backend.problem_information.infrastructure.initializer; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -6,8 +6,8 @@ import org.springframework.core.io.Resource; import org.springframework.beans.factory.annotation.Value; -import kr.co.morandi.backend.domain.algorithm.model.Algorithm; -import kr.co.morandi.backend.infrastructure.persistence.algorithm.AlgorithmRepository; +import kr.co.morandi.backend.problem_information.domain.model.algorithm.Algorithm; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.algorithm.AlgorithmRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/algorithm/AlgorithmRepository.java similarity index 57% rename from src/main/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepository.java rename to src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/algorithm/AlgorithmRepository.java index a29c496f..2f68fdd6 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepository.java +++ b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/algorithm/AlgorithmRepository.java @@ -1,6 +1,6 @@ -package kr.co.morandi.backend.infrastructure.persistence.algorithm; +package kr.co.morandi.backend.problem_information.infrastructure.persistence.algorithm; -import kr.co.morandi.backend.domain.algorithm.model.Algorithm; +import kr.co.morandi.backend.problem_information.domain.model.algorithm.Algorithm; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemAlgorithmRepository.java b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemAlgorithmRepository.java new file mode 100644 index 00000000..f1258f4d --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemAlgorithmRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.problem_information.infrastructure.persistence.problem; + +import kr.co.morandi.backend.problem_information.domain.model.problem.ProblemAlgorithm; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProblemAlgorithmRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepository.java b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepository.java similarity index 71% rename from src/main/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepository.java rename to src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepository.java index d0ca5c7a..006b61e1 100644 --- a/src/main/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepository.java +++ b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepository.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.infrastructure.persistence.problem; +package kr.co.morandi.backend.problem_information.infrastructure.persistence.problem; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.problem.model.ProblemStatus; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefenseTest.java similarity index 90% rename from src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefenseTest.java index fa98d26e..9b839ceb 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/customdefense/CustomDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefenseTest.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defense.customdefense; +package kr.co.morandi.backend.defense_information.domain.model.customdefense; -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -11,10 +11,10 @@ import java.util.Collections; import java.util.List; -import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseTier.GOLD; +import static kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.*; import static org.springframework.test.web.servlet.result.StatusResultMatchersExtensionsKt.isEqualTo; diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDetailTest.java similarity index 71% rename from src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDetailTest.java index 4daef905..5909ae1b 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/customdefense/CustomDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDetailTest.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.detail.customdefense; +package kr.co.morandi.backend.defense_information.domain.model.customdefense; -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefenseProblem; -import kr.co.morandi.backend.domain.detail.customdefense.model.CustomDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.customdefenserecord.model.CustomRecord; +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefenseProblem; +import kr.co.morandi.backend.defense_record.domain.model.customdefense_record.CustomDetail; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.customdefense_record.CustomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -13,11 +13,11 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.S5; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseTier.GOLD; +import static kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.S5; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseProblemTest.java similarity index 86% rename from src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseProblemTest.java index 8b52290b..976bb6e7 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseProblemTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseProblemTest.java @@ -1,19 +1,18 @@ -package kr.co.morandi.backend.domain.defense.dailydefense; +package kr.co.morandi.backend.defense_information.domain.model.dailydefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseTest.java similarity index 91% rename from src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseTest.java index c4a72928..4cab7fb9 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/DailyDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefenseTest.java @@ -1,8 +1,8 @@ -package kr.co.morandi.backend.domain.defense.dailydefense; +package kr.co.morandi.backend.defense_information.domain.model.dailydefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -14,7 +14,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import static org.mockito.Mockito.mock; diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDetailTest.java similarity index 85% rename from src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDetailTest.java index b4067d05..d0b912ed 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/dailydefense/DailyDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDetailTest.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.detail.dailydefense; +package kr.co.morandi.backend.defense_information.domain.model.dailydefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.detail.dailydefense.model.DailyDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -16,9 +16,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; -import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.ACTIVE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/defense/DifficultyRangeTest.java similarity index 81% rename from src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/defense/DifficultyRangeTest.java index ea1051df..1f352ed5 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/DifficultyRangeTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/defense/DifficultyRangeTest.java @@ -1,13 +1,13 @@ -package kr.co.morandi.backend.domain.defense.randomcriteria; +package kr.co.morandi.backend.defense_information.domain.model.defense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B1; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/defense/RandomCriteriaTest.java similarity index 90% rename from src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/defense/RandomCriteriaTest.java index 83a2bde6..10b5112a 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/randomcriteria/RandomCriteriaTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/defense/RandomCriteriaTest.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.domain.defense.randomcriteria; +package kr.co.morandi.backend.defense_information.domain.model.defense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDefenseTest.java similarity index 88% rename from src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDefenseTest.java index e24a8c90..42245902 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/randomdefense/RandomDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDefenseTest.java @@ -1,15 +1,15 @@ -package kr.co.morandi.backend.domain.defense.randomdefense; +package kr.co.morandi.backend.defense_information.domain.model.randomdefense; -import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B1; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDetailTest.java similarity index 84% rename from src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDetailTest.java index ee80f594..5bb0a65c 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/RandomDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDetailTest.java @@ -1,16 +1,16 @@ -package kr.co.morandi.backend.domain.detail.randomdefense; +package kr.co.morandi.backend.defense_information.domain.model.randomdefense; -import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; -import kr.co.morandi.backend.domain.detail.randomdefense.model.RandomDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.randomrecord.model.RandomRecord; +import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; +import kr.co.morandi.backend.defense_record.domain.model.randomdefense_record.RandomDetail; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.randomdefense_record.RandomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDefenseTest.java similarity index 89% rename from src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDefenseTest.java index 05a7621e..a7e922d7 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/stagedefense/StageDefenseTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDefenseTest.java @@ -1,15 +1,15 @@ -package kr.co.morandi.backend.domain.defense.stagedefense; +package kr.co.morandi.backend.defense_information.domain.model.stagedefense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.stagedefense.model.StageDefense; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B1; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDetailTest.java similarity index 85% rename from src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDetailTest.java index 1b21902d..7ae3dc42 100644 --- a/src/test/java/kr/co/morandi/backend/domain/detail/randomdefense/StageDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDetailTest.java @@ -1,17 +1,17 @@ -package kr.co.morandi.backend.domain.detail.randomdefense; +package kr.co.morandi.backend.defense_information.domain.model.stagedefense; -import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; -import kr.co.morandi.backend.domain.detail.stagedefense.model.StageDetail; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.stagerecord.model.StageRecord; +import kr.co.morandi.backend.defense_information.domain.model.stagedefense.model.StageDefense; +import kr.co.morandi.backend.defense_record.domain.model.stagedefense_record.StageDetail; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.stagedefense_record.StageRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; -import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.ACTIVE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; +import static kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java similarity index 73% rename from src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationServiceTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java index d7e88561..af6109f3 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/dailydefense/service/DailyDefenseGenerationServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java @@ -1,11 +1,12 @@ -package kr.co.morandi.backend.domain.defense.dailydefense.service; +package kr.co.morandi.backend.defense_information.domain.service.dailydefense; -import kr.co.morandi.backend.application.port.out.defense.dailydefense.DailyDefensePort; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.service.dailydefense.DailyDefenseGenerationService; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,8 +17,8 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest diff --git a/src/test/java/kr/co/morandi/backend/domain/defense/service/ProblemGenerationServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationServiceTest.java similarity index 77% rename from src/test/java/kr/co/morandi/backend/domain/defense/service/ProblemGenerationServiceTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationServiceTest.java index 6868e7df..362a3c01 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defense/service/ProblemGenerationServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationServiceTest.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.defense.service; - -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.problemgenerationstrategy.service.ProblemGenerationService; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +package kr.co.morandi.backend.defense_information.domain.service.defense; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapterTest.java similarity index 83% rename from src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapterTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapterTest.java index e9e20115..7cd85c8d 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/defense/dailydefense/DailyDefenseProblemAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapterTest.java @@ -1,11 +1,12 @@ -package kr.co.morandi.backend.infrastructure.adapter.defense.defense.dailydefense; - -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +package kr.co.morandi.backend.defense_information.infrastructure.adapter.dailydefense; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.infrastructure.adapter.dailydefense.DailyDefenseProblemAdapter; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,11 +15,10 @@ import org.springframework.test.context.ActiveProfiles; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java similarity index 84% rename from src/test/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java index 21655eea..05e19131 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.infrastructure.persistence.algorithm; +package kr.co.morandi.backend.defense_information.infrastructure.persistence.algorithm; -import kr.co.morandi.backend.domain.algorithm.model.Algorithm; -import kr.co.morandi.backend.infrastructure.persistence.algorithm.AlgorithmRepository; +import kr.co.morandi.backend.problem_information.domain.model.algorithm.Algorithm; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.algorithm.AlgorithmRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java similarity index 69% rename from src/test/java/kr/co/morandi/backend/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java index 6db35913..eaa69588 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java @@ -1,12 +1,12 @@ -package kr.co.morandi.backend.infrastructure.persistence.customdefense; - -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; -import kr.co.morandi.backend.infrastructure.persistence.defense.customdefense.CustomDefenseProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.defense.customdefense.CustomDefenseRepository; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +package kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense; + +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense.CustomDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense.CustomDefenseRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,11 +17,11 @@ import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.*; -import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.CLOSE; -import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility.CLOSE; +import static kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepositoryTest.java similarity index 75% rename from src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepositoryTest.java index e7b6f1ef..86c953b4 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/dailydefense/DailyDefenseProblemRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepositoryTest.java @@ -1,10 +1,12 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense; +package kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense; -import kr.co.morandi.backend.domain.defense.Defense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -18,7 +20,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/randomdefense/RandomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepositoryTest.java similarity index 88% rename from src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/randomdefense/RandomDefenseRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepositoryTest.java index 882659ad..1188f548 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/defense/randomdefense/RandomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepositoryTest.java @@ -1,9 +1,9 @@ -package kr.co.morandi.backend.infrastructure.persistence.defense.randomdefense; +package kr.co.morandi.backend.defense_information.infrastructure.persistence.randomdefense; -import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; -import kr.co.morandi.backend.infrastructure.persistence.defense.random.RandomDefenseRepository; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.randomdefense.RandomDefenseRepository; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,7 +13,7 @@ import java.util.List; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; diff --git a/src/test/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPortTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPortTest.java similarity index 72% rename from src/test/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPortTest.java rename to src/test/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPortTest.java index e1d4cc8a..e5d21651 100644 --- a/src/test/java/kr/co/morandi/backend/application/port/out/defensemanagement/defensesession/DefenseSessionPortTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPortTest.java @@ -1,15 +1,16 @@ -package kr.co.morandi.backend.application.port.out.defensemanagement.defensesession; - -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; -import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; -import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +package kr.co.morandi.backend.defense_management.application.port.out.session; + +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,9 +24,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest diff --git a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java similarity index 82% rename from src/test/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementServiceTest.java rename to src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java index 950bc9b3..d1f32cfc 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/management/service/DailyDefenseManagementServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java @@ -1,18 +1,19 @@ -package kr.co.morandi.backend.domain.defensemanagement.management.service; - -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defensemanagement.management.request.StartDailyDefenseServiceRequest; -import kr.co.morandi.backend.domain.defensemanagement.management.response.StartDailyDefenseServiceResponse; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; -import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; -import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.sessiondetail.SessionDetailRepository; -import kr.co.morandi.backend.infrastructure.persistence.detail.dailydetail.DailyDetailRepository; -import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord.DailyRecordRepository; +package kr.co.morandi.backend.defense_management.application.service.dailydefense; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseServiceResponse; +import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.SessionDetailRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDetailRepository; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -27,9 +28,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSessionTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java similarity index 88% rename from src/test/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSessionTest.java rename to src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java index a3a22d16..8ca8e81c 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/session/model/DefenseSessionTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java @@ -1,12 +1,13 @@ -package kr.co.morandi.backend.domain.defensemanagement.session.model; - -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model.SessionDetail; -import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +package kr.co.morandi.backend.defense_management.domain.model.session; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -19,9 +20,9 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language.CPP; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.CPP; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetailTest.java similarity index 89% rename from src/test/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetailTest.java rename to src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetailTest.java index f87276f2..e28d6447 100644 --- a/src/test/java/kr/co/morandi/backend/domain/defensemanagement/sessiondetail/model/SessionDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetailTest.java @@ -1,13 +1,14 @@ -package kr.co.morandi.backend.domain.defensemanagement.sessiondetail.model; +package kr.co.morandi.backend.defense_management.domain.model.session; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.defensemanagement.tempcode.model.TempCode; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language.CPP; -import static kr.co.morandi.backend.domain.defensemanagement.tempcode.model.Language.JAVA; +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.CPP; +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import static org.mockito.Mockito.mock; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java similarity index 71% rename from src/test/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapterTest.java rename to src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java index 56e23c11..83daf72f 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defensesession/DefenseSessionAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.infrastructure.adapter.defensesession; - -import kr.co.morandi.backend.application.port.out.defensemanagement.defensesession.DefenseSessionPort; -import kr.co.morandi.backend.domain.defensemanagement.session.model.DefenseSession; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.session.DefenseSessionRepository; -import kr.co.morandi.backend.infrastructure.persistence.defensemanagement.sessiondetail.SessionDetailRepository; -import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +package kr.co.morandi.backend.defense_management.infrastructure.adapter.session; + +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.SessionDetailRepository; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,8 +17,8 @@ import java.util.Optional; import java.util.Set; -import static kr.co.morandi.backend.domain.defense.DefenseType.DAILY; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest diff --git a/src/test/java/kr/co/morandi/backend/domain/record/customdefenserecord/CustomRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecordTest.java similarity index 88% rename from src/test/java/kr/co/morandi/backend/domain/record/customdefenserecord/CustomRecordTest.java rename to src/test/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecordTest.java index 4b4e13be..390029c9 100644 --- a/src/test/java/kr/co/morandi/backend/domain/record/customdefenserecord/CustomRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecordTest.java @@ -1,10 +1,10 @@ -package kr.co.morandi.backend.domain.record.customdefenserecord; +package kr.co.morandi.backend.defense_record.domain.model.customdefense_record; -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefense; -import kr.co.morandi.backend.domain.defense.customdefense.model.CustomDefenseProblem; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.customdefenserecord.model.CustomRecord; +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; +import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefenseProblem; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.customdefense_record.CustomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -14,11 +14,11 @@ import java.util.Map; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.customdefense.model.DefenseTier.GOLD; -import static kr.co.morandi.backend.domain.defense.customdefense.model.Visibility.OPEN; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.S5; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseTier.GOLD; +import static kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.S5; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/domain/record/dailyrecord/DailyRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java similarity index 92% rename from src/test/java/kr/co/morandi/backend/domain/record/dailyrecord/DailyRecordTest.java rename to src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java index 8ac5657e..1b970192 100644 --- a/src/test/java/kr/co/morandi/backend/domain/record/dailyrecord/DailyRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java @@ -1,10 +1,10 @@ -package kr.co.morandi.backend.domain.record.dailyrecord; +package kr.co.morandi.backend.defense_record.domain.model.dailydefense_record; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -16,8 +16,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecordTest.java similarity index 91% rename from src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java rename to src/test/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecordTest.java index fb0b24b6..7d913e4c 100644 --- a/src/test/java/kr/co/morandi/backend/domain/record/randomdefense/RandomRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecordTest.java @@ -1,10 +1,10 @@ -package kr.co.morandi.backend.domain.record.randomdefense; +package kr.co.morandi.backend.defense_record.domain.model.randomdefense_record; -import kr.co.morandi.backend.domain.defense.random.model.RandomDefense; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.randomrecord.model.RandomRecord; +import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.randomdefense_record.RandomRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -12,8 +12,8 @@ import java.time.LocalDateTime; import java.util.Map; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/domain/record/stagerecord/StageRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecordTest.java similarity index 88% rename from src/test/java/kr/co/morandi/backend/domain/record/stagerecord/StageRecordTest.java rename to src/test/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecordTest.java index 1cced10a..9d80eab5 100644 --- a/src/test/java/kr/co/morandi/backend/domain/record/stagerecord/StageRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecordTest.java @@ -1,19 +1,19 @@ -package kr.co.morandi.backend.domain.record.stagerecord; +package kr.co.morandi.backend.defense_record.domain.model.stagedefense_record; -import kr.co.morandi.backend.domain.defense.random.model.randomcriteria.RandomCriteria; -import kr.co.morandi.backend.domain.defense.stagedefense.model.StageDefense; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.stagerecord.model.StageRecord; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.stagedefense.model.StageDefense; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.stagedefense_record.StageRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B1; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.B5; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B1; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.B5; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense_record/DailyRecordAdapterTest.java similarity index 76% rename from src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapterTest.java rename to src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense_record/DailyRecordAdapterTest.java index aea85a41..84711ca6 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/defense/record/dailyrecord/DailyRecordAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense_record/DailyRecordAdapterTest.java @@ -1,15 +1,15 @@ -package kr.co.morandi.backend.infrastructure.adapter.defense.record.dailyrecord; - -import kr.co.morandi.backend.application.port.out.record.dailyrecord.DailyRecordPort; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; -import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord.DailyRecordRepository; +package kr.co.morandi.backend.defense_record.infrastructure.adapter.dailydefense_record; + +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -26,8 +26,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java similarity index 80% rename from src/test/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java index bed2c91b..b1794f6b 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/record/dailyrecord/DailyRecordRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java @@ -1,14 +1,15 @@ -package kr.co.morandi.backend.infrastructure.persistence.record.dailyrecord; - -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefense; -import kr.co.morandi.backend.domain.defense.dailydefense.model.DailyDefenseProblem; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.record.dailyrecord.model.DailyRecord; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.infrastructure.persistence.defense.dailydefense.DailyDefenseRepository; -import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; -import kr.co.morandi.backend.infrastructure.persistence.problem.ProblemRepository; +package kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,8 +26,8 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java b/src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java deleted file mode 100644 index e1676b74..00000000 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/detail/dailydetail/DailyDetailRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package kr.co.morandi.backend.infrastructure.persistence.detail.dailydetail; - -import kr.co.morandi.backend.domain.detail.dailydefense.model.DailyDetail; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DailyDetailRepository extends JpaRepository { -} diff --git a/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java b/src/test/java/kr/co/morandi/backend/member_management/domain/model/member/MemberTest.java similarity index 72% rename from src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java rename to src/test/java/kr/co/morandi/backend/member_management/domain/model/member/MemberTest.java index 508354e5..08f26db2 100644 --- a/src/test/java/kr/co/morandi/backend/domain/member/MemberTest.java +++ b/src/test/java/kr/co/morandi/backend/member_management/domain/model/member/MemberTest.java @@ -1,11 +1,11 @@ -package kr.co.morandi.backend.domain.member; +package kr.co.morandi.backend.member_management.domain.model.member; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.domain.member.model.SocialType; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; class MemberTest { diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepositoryTest.java b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java similarity index 85% rename from src/test/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java index 1123415a..bcc0beb7 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/member/MemberRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java @@ -1,7 +1,7 @@ -package kr.co.morandi.backend.infrastructure.persistence.member; +package kr.co.morandi.backend.member_management.infrastructure.persistence.member; -import kr.co.morandi.backend.domain.member.model.Member; -import kr.co.morandi.backend.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -10,7 +10,7 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.member.model.SocialType.GOOGLE; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java b/src/test/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemTest.java similarity index 72% rename from src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java rename to src/test/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemTest.java index 9796fe7e..218e3377 100644 --- a/src/test/java/kr/co/morandi/backend/domain/problem/ProblemTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/domain/model/problem/ProblemTest.java @@ -1,14 +1,14 @@ -package kr.co.morandi.backend.domain.problem; +package kr.co.morandi.backend.problem_information.domain.model.problem; -import kr.co.morandi.backend.domain.problem.model.Problem; -import kr.co.morandi.backend.domain.problem.model.ProblemStatus; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.INIT; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus.INIT; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/problem/ProblemAdapterTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problem/ProblemAdapterTest.java similarity index 57% rename from src/test/java/kr/co/morandi/backend/infrastructure/adapter/problem/ProblemAdapterTest.java rename to src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problem/ProblemAdapterTest.java index 5572d951..1154e496 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/adapter/problem/ProblemAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problem/ProblemAdapterTest.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.infrastructure.adapter.problem; +package kr.co.morandi.backend.problem_information.infrastructure.adapter.problem; import org.springframework.boot.test.context.SpringBootTest; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializerTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/config/AlgorithmInitializerTest.java similarity index 84% rename from src/test/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializerTest.java rename to src/test/java/kr/co/morandi/backend/problem_information/infrastructure/config/AlgorithmInitializerTest.java index 60bf78eb..dff7a15b 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/config/AlgorithmInitializerTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/config/AlgorithmInitializerTest.java @@ -1,7 +1,8 @@ -package kr.co.morandi.backend.infrastructure.config; +package kr.co.morandi.backend.problem_information.infrastructure.config; -import kr.co.morandi.backend.domain.algorithm.model.Algorithm; -import kr.co.morandi.backend.infrastructure.persistence.algorithm.AlgorithmRepository; +import kr.co.morandi.backend.problem_information.domain.model.algorithm.Algorithm; +import kr.co.morandi.backend.problem_information.infrastructure.initializer.AlgorithmInitializer; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.algorithm.AlgorithmRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java similarity index 82% rename from src/test/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepositoryTest.java rename to src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java index 864a0f5c..a8fd239d 100644 --- a/src/test/java/kr/co/morandi/backend/infrastructure/persistence/problem/ProblemRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java @@ -1,7 +1,8 @@ -package kr.co.morandi.backend.infrastructure.persistence.problem; +package kr.co.morandi.backend.problem_information.infrastructure.persistence.problem; -import kr.co.morandi.backend.domain.defense.tier.model.ProblemTier; -import kr.co.morandi.backend.domain.problem.model.Problem; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,8 +13,8 @@ import java.util.List; -import static kr.co.morandi.backend.domain.defense.tier.model.ProblemTier.*; -import static kr.co.morandi.backend.domain.problem.model.ProblemStatus.ACTIVE; +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus.ACTIVE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; From 50ad3ac50b97f20a25b8bc21b769ca3b4aeb6640 Mon Sep 17 00:00:00 2001 From: Jeong Yong Choi <51875177+aj4941@users.noreply.github.com> Date: Sun, 21 Apr 2024 16:52:58 +0900 Subject: [PATCH 10/14] =?UTF-8?q?Google=20OAuth=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: Google OAuth 적용 및 커스텀 예외 추가 * :pencil2: 패키지 구조 일부 변경 * :pencil2: 출력 관련 코드 제거 * :pencil2: MemberAdapter 코드 수정 * :pencil2: OAuth Login과 관련한 Cookie 발급과 관련한 작업을 Service 단에서 처리하도록 수정 * :pencil2: OAuth AccessToken 발급과 관련한 메서드 분리 * :pencil2: GoogleUserDto에 @Setter, @AllArgsConstructor 제거 * :pencil2: SetDomain 관련 url, path를 yml에 저장하여 관리 * :pencil2: ErrorCode 관련 코드 수정 및 RestControllerAdvice 코드 수정 * :pencil2: OAuth domain 관련 패키지 구조 변경 * :pencil2: LoginUseCase를 목적에 맞게 AuthenticationUseCase로 변경 (인증과 관련한 작업) * :pencil2: JwtToken 및 publicKey와 PrivateKey를 발급하는 코드 수정 * :pencil2: LoginMember에서 Repository를 호출하는 형태 변경 및 패키지 구조 일부 변경 * :pencil2: Filter를 통과하는 url 리스트를 정규 표현식으로 검사하도록 수정 * :pencil2: Spring Security Filter와 관련한 코드 수정 * :pencil2: CORS 설정을 시큐리티 기본값에서 직접 정의한 내용으로 수정 * :sparkles: RefreshToken 관리를 위한 Redis 환경 구성 * :sparkles: RefreshToken을 검증하여 accessToken을 재발급하도록 코드 추가 * :pencil2: Redis에 저장하는 refreshToken에 대한 key 값을 명확하게 하기 위해 코드 수정 * :pencil2: RefreshToken을 Redis에 저장하는 로직 수정 * :pencil2: OAuth 정보와 관련된 DTO를 OAuthUserInfo, GoogleOAuthUserInfo 형태로 변경 * :pencil2: RefreshToken을 구하는 로직 수정 * :pencil2: JwtAuthenticationFilter 구조 변경 (jwtProvider, authenticationProvider, isIgnoredURIManager로 분리) * :pencil2: JwtAuthenticationFilter에서 else if를 if로 수정 * :pencil2: AccessToken, RefreshToken을 검사하는 필터에서 주석 추가 * :pencil2: Cookie 발급 로직을 CookieUtils에서 처리하도록 수정 * :pencil2: Security, OAuth 부분 패키지 구조 변경 * :pencil2: ErrorCode 및 일부 패키지 구조 수정 * :pencil2: 사용하지 않는 ErrorCode 삭제 * :pencil2: RestControllerAdvice에서 쿠키를 받는 로직을 cookieUtils 를 사용하도록 수정 * :pencil2: OAuth 관련 패키지 구조 일부 수정 * :pencil2: Jwt 검증과 관련한 메서드를 JwtValidator로 분리 * :pencil2: Securty, Jwt, OAuth 관련 패키지 분리 * :pencil2: Domain security 관련 패키지를 Application로 이동 * :pencil2: 패키지 구조 일부 변경 * :art: RefreshToken 오류 수정 및 임시 coverage 하향 * :art: Google oauth 예외처리 * :bug: Google oauth 예외처리 --------- Co-authored-by: miiiinju1 --- build.gradle | 12 +- .../backend/common/config/CorsConfig.java | 28 +++++ .../common/config/WebClientConfig.java | 13 +++ .../common/exception/MorandiException.java | 16 +++ .../common/exception/errorcode/ErrorCode.java | 9 ++ .../handler/GlobalExceptionHandler.java | 82 ++++++++++++++ .../handler/exception/CommonErrorCode.java | 17 +++ .../exception/response/ErrorResponse.java | 25 ++++ .../model/oauth/OAuthServiceFactory.java | 21 ++++ .../port/in/oauth/AuthenticationUseCase.java | 7 ++ .../port/out/member/MemberPort.java | 12 ++ .../service/jwt/MemberLoginService.java | 28 +++++ .../service/oauth/LoginService.java | 26 +++++ .../service/oauth/OAuthService.java | 8 ++ .../service/oauth/google/GoogleService.java | 107 ++++++++++++++++++ .../security/OAuthUserDetailsService.java | 22 ++++ .../domain/model/member/Member.java | 14 ++- .../domain/model/member/Role.java | 5 + .../domain/model/member/SocialType.java | 1 - .../domain/model/security/OAuthDetails.java | 44 +++++++ .../adapter/member/MemberAdapter.java | 33 ++++++ .../config/cookie/utils/CookieUtils.java | 37 ++++++ .../config/jwt/constants/TokenType.java | 11 ++ .../jwt/response/AuthenticationToken.java | 22 ++++ .../config/jwt/utils/JwtProvider.java | 95 ++++++++++++++++ .../config/jwt/utils/JwtValidator.java | 32 ++++++ .../config/jwt/utils/SecretKeyProvider.java | 68 +++++++++++ .../config/oauth/constants/OAuthUserInfo.java | 9 ++ .../constants/google/GoogleOAuthUserInfo.java | 40 +++++++ .../config/oauth/response/TokenResponse.java | 21 ++++ .../config/security/SecurityConfig.java | 45 ++++++++ .../utils/AuthenticationProvider.java | 40 +++++++ .../security/utils/IgnoredURIManager.java | 22 ++++ .../config/security/utils/SecurityUtils.java | 11 ++ .../controller/oauth/OAuthController.java | 43 +++++++ .../controller/oauth/OAuthURLController.java | 20 ++++ .../exception/OAuthErrorCode.java | 25 ++++ .../oauth/CachedBodyHttpServletWrapper.java | 58 ++++++++++ .../filter/oauth/JwtAuthenticationFilter.java | 74 ++++++++++++ .../filter/oauth/JwtExceptionFilter.java | 83 ++++++++++++++ .../filter/oauth/RequestCachingFilter.java | 20 ++++ .../persistence/member/MemberRepository.java | 4 +- .../model/session/DefenseSessionTest.java | 6 +- .../session/DefenseSessionAdapterTest.java | 4 +- .../domain/model/member/MemberTest.java | 2 - .../member/MemberRepositoryTest.java | 10 +- 46 files changed, 1310 insertions(+), 22 deletions(-) create mode 100644 src/main/java/kr/co/morandi/backend/common/config/CorsConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java create mode 100644 src/main/java/kr/co/morandi/backend/common/exception/errorcode/ErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java create mode 100644 src/main/java/kr/co/morandi/backend/common/exception/handler/exception/CommonErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/common/exception/response/ErrorResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/model/oauth/OAuthServiceFactory.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/port/in/oauth/AuthenticationUseCase.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/service/jwt/MemberLoginService.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/LoginService.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/OAuthService.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/google/GoogleService.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/application/service/security/OAuthUserDetailsService.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Role.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/domain/model/security/OAuthDetails.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/cookie/utils/CookieUtils.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/constants/TokenType.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/response/AuthenticationToken.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtValidator.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/SecretKeyProvider.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/OAuthUserInfo.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/google/GoogleOAuthUserInfo.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/response/TokenResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthController.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthURLController.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/CachedBodyHttpServletWrapper.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtAuthenticationFilter.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtExceptionFilter.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/RequestCachingFilter.java diff --git a/build.gradle b/build.gradle index e0487984..2e887386 100644 --- a/build.gradle +++ b/build.gradle @@ -82,6 +82,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' + + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2' + runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' compileOnly 'org.projectlombok:lombok' @@ -137,13 +145,13 @@ jacocoTestCoverageVerification { limit { counter = 'LINE' value = 'COVEREDRATIO' - minimum = 0.50 + minimum = 0.40 } limit { counter = 'BRANCH' value = 'COVEREDRATIO' - minimum = 0.50 + minimum = 0.40 } diff --git a/src/main/java/kr/co/morandi/backend/common/config/CorsConfig.java b/src/main/java/kr/co/morandi/backend/common/config/CorsConfig.java new file mode 100644 index 00000000..b90429eb --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/config/CorsConfig.java @@ -0,0 +1,28 @@ +package kr.co.morandi.backend.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@Configuration +public class CorsConfig { + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.setAllowedOrigins(List.of("http://localhost:3000", "http://localhost:8080", "http://morandi.co.kr", + "https://morandi.co.kr", "http://api.morandi.co.kr", "https://api.morandi.co.kr")); + + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); + config.setExposedHeaders(List.of("*")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } +} diff --git a/src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java b/src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java new file mode 100644 index 00000000..c06228be --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java @@ -0,0 +1,13 @@ +package kr.co.morandi.backend.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + @Bean + public WebClient webClient() { + return WebClient.builder().build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java b/src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java new file mode 100644 index 00000000..66dec4f0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java @@ -0,0 +1,16 @@ +package kr.co.morandi.backend.common.exception; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +@Getter +public class MorandiException extends RuntimeException { + + private final ErrorCode errorCode; + + public MorandiException(ErrorCode errorCode) { + this.errorCode = errorCode; + } + public MorandiException(ErrorCode errorCode, String message) { + this.errorCode = errorCode; + } +} diff --git a/src/main/java/kr/co/morandi/backend/common/exception/errorcode/ErrorCode.java b/src/main/java/kr/co/morandi/backend/common/exception/errorcode/ErrorCode.java new file mode 100644 index 00000000..71ecbff6 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/exception/errorcode/ErrorCode.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend.common.exception.errorcode; + +import org.springframework.http.HttpStatus; + +public interface ErrorCode { + String name(); + HttpStatus getHttpStatus(); + String getMessage(); +} diff --git a/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java b/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..3efd9cfc --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,82 @@ +package kr.co.morandi.backend.common.exception.handler; + +import jakarta.servlet.http.Cookie; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import kr.co.morandi.backend.common.exception.response.ErrorResponse; +import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.NoHandlerFoundException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import java.net.URI; + +import static kr.co.morandi.backend.common.exception.handler.exception.CommonErrorCode.INTERNAL_SERVER_ERROR; +import static kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType.REFRESH_TOKEN; + +@Slf4j +@RestControllerAdvice +@RequiredArgsConstructor +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + private final CookieUtils cookieUtils; + + @Value("${oauth2.signup-url}") + private String signupPath; + + @ExceptionHandler(MorandiException.class) + public ResponseEntity morandiExceptionHandler(MorandiException e) { + log.error(e.getErrorCode().name()+" : ", e.getErrorCode().getMessage() + " : ", e); + + // Unauthorized 에러가 발생한 경우 + if (e.getErrorCode().getHttpStatus() == HttpStatus.UNAUTHORIZED) { + HttpHeaders headers = createUnauthorizedHeaders(); + + // 로그인 페이지로 리다이렉트 + return new ResponseEntity<>(headers, HttpStatus.FOUND); + } + + // 그 외의 에러가 발생한 경우 + return handleException(e.getErrorCode()); + } + + /** + * 예상 외의 에러가 발생한 경우 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllException(Exception e) { + log.error(INTERNAL_SERVER_ERROR.name() + " : ", e); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(e.getMessage()); + } + + /** + * Unauthorized 에러가 발생한 경우 + * Refresh Token 쿠키를 제거하고 + * 로그인 페이지로 리다이렉트 + */ + private HttpHeaders createUnauthorizedHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(URI.create(signupPath)); + Cookie cookie = cookieUtils.removeCookie(REFRESH_TOKEN, null); + headers.add("Set-Cookie", cookie.toString()); + return headers; + } + + /** + * 에러 응답 생성 + */ + private ResponseEntity handleException(ErrorCode errorCode) { + return ResponseEntity.status(errorCode.getHttpStatus()) + .body(ErrorResponse.of(errorCode)); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/common/exception/handler/exception/CommonErrorCode.java b/src/main/java/kr/co/morandi/backend/common/exception/handler/exception/CommonErrorCode.java new file mode 100644 index 00000000..719ea45a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/exception/handler/exception/CommonErrorCode.java @@ -0,0 +1,17 @@ +package kr.co.morandi.backend.common.exception.handler.exception; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum CommonErrorCode implements ErrorCode { + + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "내부 서버 오류입니다."); + + private final HttpStatus httpStatus; + + private final String message; +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/common/exception/response/ErrorResponse.java b/src/main/java/kr/co/morandi/backend/common/exception/response/ErrorResponse.java new file mode 100644 index 00000000..dddbc644 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/exception/response/ErrorResponse.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.common.exception.response; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class ErrorResponse { + + private final String code; + private final String message; + + public static ErrorResponse of(ErrorCode errorCode) { + return ErrorResponse.builder() + .code(errorCode.name()) + .message(errorCode.getMessage()) + .build(); + } + @Builder + private ErrorResponse(String code, String message) { + this.code = code; + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/model/oauth/OAuthServiceFactory.java b/src/main/java/kr/co/morandi/backend/member_management/application/model/oauth/OAuthServiceFactory.java new file mode 100644 index 00000000..eff1d738 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/model/oauth/OAuthServiceFactory.java @@ -0,0 +1,21 @@ +package kr.co.morandi.backend.member_management.application.model.oauth; + +import kr.co.morandi.backend.member_management.application.service.oauth.OAuthService; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +public class OAuthServiceFactory { + private final Map serviceMap; + public OAuthServiceFactory(List oAuthServices) { + this.serviceMap = oAuthServices.stream() + .collect(Collectors.toMap(OAuthService::getType, Function.identity())); + } + public OAuthService getServiceByType(String type) { + return serviceMap.get(type); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/port/in/oauth/AuthenticationUseCase.java b/src/main/java/kr/co/morandi/backend/member_management/application/port/in/oauth/AuthenticationUseCase.java new file mode 100644 index 00000000..14ce3dfc --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/port/in/oauth/AuthenticationUseCase.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.member_management.application.port.in.oauth; + +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.response.AuthenticationToken; + +public interface AuthenticationUseCase { + AuthenticationToken getAuthenticationToken(String type, String authenticationCode); +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java b/src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java new file mode 100644 index 00000000..11285144 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java @@ -0,0 +1,12 @@ +package kr.co.morandi.backend.member_management.application.port.out.member; + +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; + +import java.util.Optional; + +public interface MemberPort { + Member saveMemberByEmail(String email, SocialType type); + Member findMemberById(Long memberId); + Optional findMemberByEmail(String email); +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/service/jwt/MemberLoginService.java b/src/main/java/kr/co/morandi/backend/member_management/application/service/jwt/MemberLoginService.java new file mode 100644 index 00000000..306b5841 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/service/jwt/MemberLoginService.java @@ -0,0 +1,28 @@ +package kr.co.morandi.backend.member_management.application.service.jwt; + +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.JwtProvider; +import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants.OAuthUserInfo; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.response.AuthenticationToken; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class MemberLoginService { + + private final JwtProvider jwtProvider; + + private final MemberPort memberPort; + public AuthenticationToken loginMember(OAuthUserInfo oAuthUserInfo) { + Optional maybeMember = memberPort.findMemberByEmail(oAuthUserInfo.getEmail()); + Member member = maybeMember.isPresent() + ? maybeMember.get() : memberPort.saveMemberByEmail(oAuthUserInfo.getEmail(), oAuthUserInfo.getType()); + return jwtProvider.getAuthenticationToken(member); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/LoginService.java b/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/LoginService.java new file mode 100644 index 00000000..c158e86a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/LoginService.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.member_management.application.service.oauth; + +import kr.co.morandi.backend.member_management.application.port.in.oauth.AuthenticationUseCase; +import kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants.OAuthUserInfo; +import kr.co.morandi.backend.member_management.application.model.oauth.OAuthServiceFactory; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.response.AuthenticationToken; +import kr.co.morandi.backend.member_management.application.service.jwt.MemberLoginService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LoginService implements AuthenticationUseCase { + + private final OAuthServiceFactory oAuthServiceFactory; + + private final MemberLoginService memberLoginService; + @Override + public AuthenticationToken getAuthenticationToken(String type, String authenticationCode) { + OAuthService oAuthService = oAuthServiceFactory.getServiceByType(type); + String oAuthAccessToken = oAuthService.getAccessToken(authenticationCode); + OAuthUserInfo oAuthUserInfo = oAuthService.getUserInfo(oAuthAccessToken); + AuthenticationToken authenticationToken = memberLoginService.loginMember(oAuthUserInfo); + return authenticationToken; + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/OAuthService.java b/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/OAuthService.java new file mode 100644 index 00000000..a3a8033c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/OAuthService.java @@ -0,0 +1,8 @@ +package kr.co.morandi.backend.member_management.application.service.oauth; +import kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants.OAuthUserInfo; + +public interface OAuthService { + String getType(); + String getAccessToken(String authorizationCode); + OAuthUserInfo getUserInfo(String accessToken); +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/google/GoogleService.java b/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/google/GoogleService.java new file mode 100644 index 00000000..0389020c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/service/oauth/google/GoogleService.java @@ -0,0 +1,107 @@ +package kr.co.morandi.backend.member_management.application.service.oauth.google; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants.OAuthUserInfo; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; +import kr.co.morandi.backend.member_management.infrastructure.config.oauth.response.TokenResponse; +import kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants.google.GoogleOAuthUserInfo; +import kr.co.morandi.backend.member_management.application.service.oauth.OAuthService; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +@Service +@RequiredArgsConstructor +@Slf4j +public class GoogleService implements OAuthService { + + @Value("${oauth2.google.client-id}") + private String googleClientId; + + @Value("${oauth2.google.client-secret}") + private String googleClientSecret; + + @Value("${oauth2.google.redirect-callback-url}") + private String googleClientRedirectUrl; + + @Value("${oauth2.google.api-token-url}") + private String googleApiTokenUrl; + + @Value("${oauth2.google.userinfo-url}") + private String googleUserInfoUrl; + + @Value("${oauth2.google.type}") + private String type; + + private final WebClient webClient; + @Override + public String getType() { + return type; + } + @Override + public String getAccessToken(String authorizationCode) { + LinkedMultiValueMap params = buildParams(authorizationCode); + TokenResponse tokenResponse = getTokenResponse(params); + + return tokenResponse.getAccess_token(); + } + private LinkedMultiValueMap buildParams(String authorizationCode) { + LinkedMultiValueMap params = new LinkedMultiValueMap<>(); + params.add("code", authorizationCode); + params.add("client_id", googleClientId); + params.add("client_secret", googleClientSecret); + params.add("grant_type", "authorization_code"); + params.add("redirect_uri", googleClientRedirectUrl); + return params; + } + private TokenResponse getTokenResponse(LinkedMultiValueMap params) { + TokenResponse tokenResponse = webClient.post() + .uri(googleApiTokenUrl) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromValue(params)) + .retrieve() + .bodyToMono(TokenResponse.class) + .retry(3) + .block(); + + if(tokenResponse == null) { + throw new MorandiException(OAuthErrorCode.GOOGLE_OAUTH_ERROR); + } + + return tokenResponse; + } + + @Override + public OAuthUserInfo getUserInfo(String accessToken) { + HttpHeaders headers = getBearerHeader(accessToken); + return getGoogleUserDto(headers); + } + private HttpHeaders getBearerHeader(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); + return headers; + } + private GoogleOAuthUserInfo getGoogleUserDto(HttpHeaders headers) { + GoogleOAuthUserInfo googleUserDto = webClient.get() + .uri(googleUserInfoUrl) + .headers(httpHeaders -> httpHeaders.addAll(headers)) + .retrieve() + .bodyToMono(GoogleOAuthUserInfo.class) + .retry(3) + .block(); + + if(googleUserDto == null) { + throw new MorandiException(OAuthErrorCode.GOOGLE_OAUTH_ERROR); + } + + googleUserDto.setSocialType(SocialType.GOOGLE); + return googleUserDto; + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/service/security/OAuthUserDetailsService.java b/src/main/java/kr/co/morandi/backend/member_management/application/service/security/OAuthUserDetailsService.java new file mode 100644 index 00000000..7aeb08ec --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/application/service/security/OAuthUserDetailsService.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.member_management.application.service.security; + +import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.domain.model.security.OAuthDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class OAuthUserDetailsService implements UserDetailsService { + + private final MemberPort memberPort; + @Override + public UserDetails loadUserByUsername(String memberId) throws UsernameNotFoundException { + Member member = memberPort.findMemberById(Long.parseLong(memberId)); + return new OAuthDetails(memberId, member.getBaekjoonId()); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java index 4f7c569e..9528c18d 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java @@ -25,9 +25,9 @@ public class Member extends BaseEntity { private String profileImageURL; private String description; - @Builder - private Member(String nickname, String baekjoonId, String email, SocialType socialType, String profileImageURL, String description) { + private Member(String nickname, String baekjoonId, String email, + SocialType socialType, String profileImageURL, String description) { this.nickname = nickname; this.baekjoonId = baekjoonId; this.email = email; @@ -35,8 +35,8 @@ private Member(String nickname, String baekjoonId, String email, SocialType soci this.profileImageURL = profileImageURL; this.description = description; } - - public static Member create(String nickname, String email, SocialType socialType, String profileImageURL, String description) { + public static Member create(String nickname, String email, SocialType socialType, + String profileImageURL, String description) { return Member.builder() .nickname(nickname) .email(email) @@ -45,4 +45,10 @@ public static Member create(String nickname, String email, SocialType socialType .description(description) .build(); } + public static Member create(String email, SocialType socialType) { + return Member.builder() + .email(email) + .socialType(socialType) + .build(); + } } diff --git a/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Role.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Role.java new file mode 100644 index 00000000..5e91c88a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Role.java @@ -0,0 +1,5 @@ +package kr.co.morandi.backend.member_management.domain.model.member; + +public enum Role { + USER, ADMIN +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/SocialType.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/SocialType.java index 13a36697..7c94f272 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/SocialType.java +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/SocialType.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; - @Getter @RequiredArgsConstructor public enum SocialType { diff --git a/src/main/java/kr/co/morandi/backend/member_management/domain/model/security/OAuthDetails.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/security/OAuthDetails.java new file mode 100644 index 00000000..23d06887 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/security/OAuthDetails.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend.member_management.domain.model.security; + +import lombok.AllArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +@AllArgsConstructor +public class OAuthDetails implements UserDetails { + + private String memberId; + + private String baekjoonId; + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + @Override + public String getPassword() { + return null; + } + @Override + public String getUsername() { + return memberId; + } + @Override + public boolean isAccountNonExpired() { + return true; + } + @Override + public boolean isAccountNonLocked() { + return true; + } + @Override + public boolean isCredentialsNonExpired() { + return true; + } + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java new file mode 100644 index 00000000..222b8e28 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.member_management.infrastructure.adapter.member; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class MemberAdapter implements MemberPort { + + private final MemberRepository memberRepository; + @Override + public Member saveMemberByEmail(String email, SocialType type) { + return memberRepository.save(Member.create(email, type)); + } + @Override + public Optional findMemberByEmail(String email) { + return memberRepository.findByEmail(email); + } + @Override + public Member findMemberById(Long memberId) { + return memberRepository.findById(memberId) + .orElseThrow(() -> new MorandiException(OAuthErrorCode.MEMBER_NOT_FOUND)); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/cookie/utils/CookieUtils.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/cookie/utils/CookieUtils.java new file mode 100644 index 00000000..27b422a7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/cookie/utils/CookieUtils.java @@ -0,0 +1,37 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils; + +import jakarta.servlet.http.Cookie; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class CookieUtils { + + @Value("${oauth2.cookie.domain}") + private String domain; + + @Value("${oauth2.cookie.path}") + private String path; + + private final int COOKIE_AGE = 60 * 60 * 24 * 10; + private final Integer COOKIE_REMOVE_AGE = 0; + + public Cookie getCookie(TokenType type, String value) { + Cookie cookie = new Cookie(type.name(), value); + cookie.setHttpOnly(true); + cookie.setMaxAge(COOKIE_AGE); + cookie.setDomain(domain); + cookie.setPath(path); + return cookie; + } + + public Cookie removeCookie(TokenType type, String value) { + Cookie cookie = new Cookie(type.name(), value); + cookie.setHttpOnly(true); + cookie.setMaxAge(COOKIE_REMOVE_AGE); + cookie.setDomain(domain); + cookie.setPath(path); + return cookie; + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/constants/TokenType.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/constants/TokenType.java new file mode 100644 index 00000000..6fd2ff99 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/constants/TokenType.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum TokenType { + ACCESS_TOKEN("accessToken"), + REFRESH_TOKEN("refreshToken"); + + private final String name; +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/response/AuthenticationToken.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/response/AuthenticationToken.java new file mode 100644 index 00000000..6cc82376 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/response/AuthenticationToken.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.jwt.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class AuthenticationToken { + + private String accessToken; + + private String refreshToken; + public static AuthenticationToken create(String accessToken, String refreshToken) { + return AuthenticationToken.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } +} + diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java new file mode 100644 index 00000000..affc3525 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java @@ -0,0 +1,95 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils; + +import io.jsonwebtoken.*; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.domain.model.member.Role; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.response.AuthenticationToken; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.util.WebUtils; +import java.security.PrivateKey; +import java.util.Date; + +@RequiredArgsConstructor +@Slf4j +@Component +public class JwtProvider { + + private final Long ACCESS_TOKEN_EXPIRATION = 60 * 60 * 3 * 1000L; // 3 hours + private final Long REFRESH_TOKEN_EXPIRATION = 60 * 60 * 24 * 7 * 1000L; // 7 days + + private final SecretKeyProvider secretKeyProvider; + + public AuthenticationToken getAuthenticationToken(Member member) { + String accessToken = generateAccessToken(member.getMemberId(), Role.USER); + String refreshToken = generateRefreshToken(member.getMemberId(), Role.USER); + return AuthenticationToken.create(accessToken, refreshToken); + } + + public String parseAccessToken(HttpServletRequest request) { + String accessToken = request.getHeader("Authorization"); + if (StringUtils.hasText(accessToken) && accessToken.startsWith("Bearer ")) { + return accessToken.substring(7); + } + throw new MorandiException(OAuthErrorCode.ACCESS_TOKEN_NOT_FOUND); + } + public String parseRefreshToken(HttpServletRequest request) { + Cookie cookie = WebUtils.getCookie(request, "REFRESH_TOKEN"); + if(cookie==null) + throw new MorandiException(OAuthErrorCode.REFRESH_TOKEN_NOT_FOUND); + + return cookie.getValue(); + } + public String reissueAccessToken(String refreshToken) { + Long memberId = getMemberIdFromToken(refreshToken); + + return generateAccessToken(memberId, Role.USER); + } + + private String generateAccessToken(Long id, Role role) { + final Date issuedAt = new Date(); + final Date accessTokenExpiresIn = new Date(issuedAt.getTime() + ACCESS_TOKEN_EXPIRATION); + return buildAccessToken(id, issuedAt, accessTokenExpiresIn, role); + } + private String generateRefreshToken(Long id, Role role) { + final Date issuedAt = new Date(); + final Date refreshTokenExpiresIn = new Date(issuedAt.getTime() + REFRESH_TOKEN_EXPIRATION); + return buildRefreshToken(id, issuedAt, refreshTokenExpiresIn, role); + } + private String buildAccessToken(Long id, Date issuedAt, Date expiresIn, Role role) { + final PrivateKey encodedKey = secretKeyProvider.getPrivateKey(); + return jwtCreate(id, issuedAt, expiresIn, role, encodedKey, TokenType.ACCESS_TOKEN); + } + private String buildRefreshToken(Long id, Date issuedAt, Date expiresIn, Role role) { + final PrivateKey encodedKey = secretKeyProvider.getPrivateKey(); + + return jwtCreate(id, issuedAt, expiresIn, role, encodedKey, TokenType.REFRESH_TOKEN); + } + + private String jwtCreate(Long id, Date issuedAt, Date expiresIn, Role role, + PrivateKey encodedKey, TokenType tokenType) { + return Jwts.builder() + .setIssuer("MORANDI") + .setIssuedAt(issuedAt) + .setSubject(id.toString()) + .claim("type", tokenType) + .claim("role", role) + .setExpiration(expiresIn) + .signWith(encodedKey) + .compact(); + } + private Long getMemberIdFromToken(String token) { + Jws claimsJws = Jwts.parserBuilder().setSigningKey(secretKeyProvider.getPublicKey()) + .build() + .parseClaimsJws(token); + Claims claims = claimsJws.getBody(); + return Long.parseLong(claims.getSubject()); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtValidator.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtValidator.java new file mode 100644 index 00000000..55458dbd --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtValidator.java @@ -0,0 +1,32 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils; + +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +@Component +@RequiredArgsConstructor +public class JwtValidator { + + private final SecretKeyProvider secretKeyProvider; + public boolean validateToken(String token) { + if (!StringUtils.hasText(token)) + throw new MorandiException(OAuthErrorCode.INVALID_TOKEN); + try { + Jwts.parserBuilder() + .setSigningKey(secretKeyProvider.getPublicKey()) + .build() + .parseClaimsJws(token); + return true; + } catch (ExpiredJwtException e) { + return false; + } catch (JwtException e) { + throw new MorandiException(OAuthErrorCode.INVALID_TOKEN); + } + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/SecretKeyProvider.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/SecretKeyProvider.java new file mode 100644 index 00000000..3f4a4035 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/SecretKeyProvider.java @@ -0,0 +1,68 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +@Component +@Getter +public class SecretKeyProvider { + + private final PublicKey publicKey; + + private final PrivateKey privateKey; + + public SecretKeyProvider(@Value("${security.publicKey}") String publicKey, + @Value("${security.privateKey}") String privateKey) { + this.publicKey = convertPEMToPublicKey(decoding(publicKey)); + this.privateKey = convertPEMToPrivateKey(decoding(privateKey)); + } + private String decoding(String key) { + byte[] decoded = Base64.getDecoder().decode(key); + return new String(decoded, StandardCharsets.UTF_8); + } + public PublicKey convertPEMToPublicKey(String publicKeyPemFile) { + String publicKeyPEM = extractPublicPemKeyContent(publicKeyPemFile); + byte[] encodedKey = Base64.getDecoder().decode(publicKeyPEM); + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey); + return keyFactory.generatePublic(keySpec); + } + catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } + private PrivateKey convertPEMToPrivateKey(String privateKeyPemFile) { + String privateKeyPEM = extractPrivatePemKeyContent(privateKeyPemFile); + byte[] encodedKey = Base64.getDecoder().decode(privateKeyPEM); + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedKey); + return keyFactory.generatePrivate(keySpec); + } + catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException(e); + } + } + private String extractPublicPemKeyContent(String pemKey) { + return pemKey.replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s", ""); + } + + private String extractPrivatePemKeyContent(String pemKey) { + return pemKey.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s", ""); + } +} + diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/OAuthUserInfo.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/OAuthUserInfo.java new file mode 100644 index 00000000..c3dc0e87 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/OAuthUserInfo.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants; + +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; + +public interface OAuthUserInfo { + SocialType getType(); + String getEmail(); + String getPicture(); +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/google/GoogleOAuthUserInfo.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/google/GoogleOAuthUserInfo.java new file mode 100644 index 00000000..cfe90c97 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/constants/google/GoogleOAuthUserInfo.java @@ -0,0 +1,40 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants.google; + +import kr.co.morandi.backend.member_management.infrastructure.config.oauth.constants.OAuthUserInfo; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class GoogleOAuthUserInfo implements OAuthUserInfo { + + private String id; + private String email; + private String verified_email; + private String hd; + private String name; + private String given_name; + private String family_name; + private String picture; + private String locale; + private SocialType type; + + @Override + public SocialType getType() { + return type; + } + public void setSocialType(SocialType type) { + this.type = type; + } + + @Override + public String getEmail() { + return email; + } + + @Override + public String getPicture() { + return picture; + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/response/TokenResponse.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/response/TokenResponse.java new file mode 100644 index 00000000..83342eac --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/oauth/response/TokenResponse.java @@ -0,0 +1,21 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.oauth.response; + +import lombok.Getter; + +@Getter +public class TokenResponse { + + public String token_type; + + public String access_token; + + public String id_token; + + public Integer expires_in; + + public String refresh_token; + + public Integer refresh_token_expires_in; + + public String scope; +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java new file mode 100644 index 00000000..0cbeb7a3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java @@ -0,0 +1,45 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.security; + +import kr.co.morandi.backend.member_management.infrastructure.filter.oauth.JwtAuthenticationFilter; +import kr.co.morandi.backend.member_management.infrastructure.filter.oauth.JwtExceptionFilter; +import kr.co.morandi.backend.member_management.infrastructure.filter.oauth.RequestCachingFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.web.cors.CorsConfigurationSource; + +@EnableWebSecurity +@Configuration +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final JwtExceptionFilter jwtExceptionFilter; + private final RequestCachingFilter requestCachingFilter; + private final CorsConfigurationSource corsConfigurationSource; + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ + http + .httpBasic(HttpBasicConfigurer::disable) + .csrf(CsrfConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource)) + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/oauths/**","/swagger-ui/**", "/swagger-resources/**", + "/v3/api-docs/**").permitAll() + .anyRequest().authenticated()) + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(jwtAuthenticationFilter, BasicAuthenticationFilter.class) + .addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class) + .addFilterBefore(requestCachingFilter, JwtExceptionFilter.class); + + return http.build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java new file mode 100644 index 00000000..7a54b97c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java @@ -0,0 +1,40 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.security.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import kr.co.morandi.backend.member_management.application.service.security.OAuthUserDetailsService; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.SecretKeyProvider; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class AuthenticationProvider { + + private final OAuthUserDetailsService oAuthUserDetailsService; + + private final SecretKeyProvider secretKeyProvider; + public void setAuthentication(String accessToken) { + Authentication authentication = getAuthentication(accessToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + private Authentication getAuthentication(String accessToken) { + Long memberId = getMemberIdFromToken(accessToken); + UserDetails userDetails = oAuthUserDetailsService.loadUserByUsername(memberId.toString()); + return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + } + private Long getMemberIdFromToken(String token) { + Jws claimsJws = Jwts.parserBuilder().setSigningKey(secretKeyProvider.getPublicKey()) + .build() + .parseClaimsJws(token); + Claims claims = claimsJws.getBody(); + return Long.parseLong(claims.getSubject()); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java new file mode 100644 index 00000000..1d932ec1 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.security.utils; + +import org.springframework.stereotype.Component; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Component +public class IgnoredURIManager { + private static final String[] IGNORED_URIS = { + "/oauths/", + "/swagger-ui/", + "/v3/api-docs/", + "/swagger-resources/" + }; + private String PATTERN_STRING = String.join("|", IGNORED_URIS); + public Pattern PATTERN = Pattern.compile(PATTERN_STRING); + public boolean isIgnoredURI(String uri) { + Matcher matcher = PATTERN.matcher(uri); + return matcher.find(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java new file mode 100644 index 00000000..b28f5dc5 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.member_management.infrastructure.config.security.utils; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +public class SecurityUtils { + public static Long getCurrentMemberId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return Long.valueOf(authentication.getName()); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthController.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthController.java new file mode 100644 index 00000000..e820d5ab --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthController.java @@ -0,0 +1,43 @@ +package kr.co.morandi.backend.member_management.infrastructure.controller.oauth; + +import jakarta.servlet.http.HttpServletResponse; +import kr.co.morandi.backend.member_management.application.port.in.oauth.AuthenticationUseCase; +import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.response.AuthenticationToken; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; + +import static kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType.REFRESH_TOKEN; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/oauths") +@Slf4j +public class OAuthController { + + private final AuthenticationUseCase authenticationUseCase; + + private final CookieUtils cookieUtils; + + @Value("${morandi.redirect-url}") + private String redirectUrl; + + @GetMapping("/{type}/callback") + public ResponseEntity OAuthLogin(@PathVariable String type, + @RequestParam String code, + HttpServletResponse response) { + AuthenticationToken authenticationToken = authenticationUseCase.getAuthenticationToken(type, code); + response.setHeader("Authorization", "Bearer " + authenticationToken.getAccessToken()); + response.addCookie(cookieUtils.getCookie(REFRESH_TOKEN, authenticationToken.getRefreshToken())); + return ResponseEntity.status(HttpStatus.FOUND) + .location(URI.create(redirectUrl)) + .build(); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthURLController.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthURLController.java new file mode 100644 index 00000000..0a4fe6d1 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/controller/oauth/OAuthURLController.java @@ -0,0 +1,20 @@ +package kr.co.morandi.backend.member_management.infrastructure.controller.oauth; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/oauths") +@RequiredArgsConstructor +public class OAuthURLController { + + @Value("${oauth2.google.redirect-url}") + private String googleRedirectUrl; + @GetMapping("/google") + public String googleRedirect() { + return "redirect:" + googleRedirectUrl; + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java new file mode 100644 index 00000000..c4ddb706 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.member_management.infrastructure.exception; + + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum OAuthErrorCode implements ErrorCode { + + MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST,"사용자를 찾을 수 없습니다"), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED,"인증 시간이 만료된 토큰입니다."), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED,"유효하지 않은 토큰입니다."), + TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "액세스 토큰을 찾을 수 없습니다"), + ACCESS_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "액세스 토큰을 찾을 수 없습니다."), + REFRESH_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "리프레시 토큰을 찾을 수 없습니다."), + UNKNOWN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"알 수 없는 오류"), + GOOGLE_OAUTH_ERROR(HttpStatus.BAD_REQUEST,"구글 OAuth 인증을 실패했습니다."); + + + private final HttpStatus httpStatus; + private final String message; +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/CachedBodyHttpServletWrapper.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/CachedBodyHttpServletWrapper.java new file mode 100644 index 00000000..a675a9be --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/CachedBodyHttpServletWrapper.java @@ -0,0 +1,58 @@ +package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; + +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import lombok.SneakyThrows; +import org.springframework.util.StreamUtils; + +import java.io.*; + +public class CachedBodyHttpServletWrapper extends HttpServletRequestWrapper { + private final byte[] cachedBody; + + public CachedBodyHttpServletWrapper(HttpServletRequest request) throws IOException { + super(request); + InputStream requestInputStream = request.getInputStream(); + this.cachedBody = StreamUtils.copyToByteArray(requestInputStream); + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return new CachedBodyServletInputStream(this.cachedBody); + } + + @Override + public BufferedReader getReader() throws IOException { + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); + return new BufferedReader(new InputStreamReader(byteArrayInputStream, "UTF-8")); + } + public static class CachedBodyServletInputStream extends ServletInputStream { + + private final InputStream cachedBodyInputStream; + + public CachedBodyServletInputStream(byte[] cachedBody) { + this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody); + } + @SneakyThrows + @Override + public boolean isFinished() { + return cachedBodyInputStream.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener listener) { + + } + @Override + public int read() throws IOException { + return cachedBodyInputStream.read(); + } + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtAuthenticationFilter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtAuthenticationFilter.java new file mode 100644 index 00000000..11a0e398 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtAuthenticationFilter.java @@ -0,0 +1,74 @@ +package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.JwtValidator; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.JwtProvider; +import kr.co.morandi.backend.member_management.infrastructure.config.security.utils.AuthenticationProvider; +import kr.co.morandi.backend.member_management.infrastructure.config.security.utils.IgnoredURIManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtProvider jwtProvider; + + private final JwtValidator jwtValidator; + + private final AuthenticationProvider authenticationProvider; + + private final IgnoredURIManager isIgnoredURIManager; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + /* + * 인증 필요 없는 URI인 경우 필터링하지 않고 바로 다음 필터로 넘어간다. + * */ + if (isIgnoredURIManager.isIgnoredURI(request.getRequestURI())) { + filterChain.doFilter(request, response); + return; + } + + String accessToken = jwtProvider.parseAccessToken(request); + String refreshToken = jwtProvider.parseRefreshToken(request); + + /* + * accessToken이 유효한 경우, accessToken을 이용하여 인증을 수행하고 다음 필터로 넘어간다. + * */ + if (jwtValidator.validateToken(accessToken)) { // accessToken이 유효할 경우 + authenticationProvider.setAuthentication(accessToken); + + filterChain.doFilter(request, response); + return ; + } + + /* + * accessToken의 유효 기간이 만료된 경우, refreshToken을 이용하여 accessToken을 재발급하고 다음 필터로 넘어간다. + * */ + if (jwtValidator.validateToken(refreshToken)) { // refreshToken이 유효할 경우 + accessToken = jwtProvider.reissueAccessToken(refreshToken); + response.setHeader("Authorization", "Bearer " + accessToken); + + authenticationProvider.setAuthentication(accessToken); + filterChain.doFilter(request, response); + return ; + } + + /* + * refreshToken의 유효 기간도 만료된 경우, refreshToken이 만료되었다는 오류를 반환한다. + * */ + throw new MorandiException(OAuthErrorCode.EXPIRED_TOKEN); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtExceptionFilter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtExceptionFilter.java new file mode 100644 index 00000000..cdda741a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtExceptionFilter.java @@ -0,0 +1,83 @@ +package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import kr.co.morandi.backend.common.exception.response.ErrorResponse; +import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +import static kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType.REFRESH_TOKEN; + +@Component +@RequiredArgsConstructor +@Slf4j +public class JwtExceptionFilter extends OncePerRequestFilter { + + private final ObjectMapper objectMapper; + + private final CookieUtils cookieUtils; + + @Value("${oauth2.signup-url}") + private String signupPath; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws IOException { + try { + /* + * 다음 필터인 JwtAuthenticationFilter에서 발생한 예외를 처리하기 위해 필터를 실행한다. + * */ + filterChain.doFilter(request, response); + } catch (MorandiException e) { + /* + * JwtAuthenticationFilter에서 발생한 예외가 인증 오류인 경우, refreshToken을 제거하고 로그인 페이지로 리다이렉트한다. + * */ + if (isAuthError(e)) { + Cookie cookie = cookieUtils.removeCookie(REFRESH_TOKEN,null); + response.addCookie(cookie); + response.sendRedirect(signupPath); + } + + /* + * JwtAuthenticationFilter에서 발생한 예외가 인증 오류가 아닌 경우, 예외에 해당하는 오류 응답을 반환한다. + */ + setErrorResponse(response, e.getErrorCode()); + } catch (Exception e) { + /* + * JwtAuthenticationFilter에서 발생한 예외가 MorandiException이 아닌 경우, 알 수 없는 오류 응답을 반환한다. + * */ + setErrorResponse(response, OAuthErrorCode.UNKNOWN_ERROR); + } + } + private boolean isAuthError(MorandiException e) { + return e.getErrorCode().getHttpStatus() == (HttpStatus.UNAUTHORIZED); + } + private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode) { + response.setStatus(errorCode.getHttpStatus().value()); + response.setCharacterEncoding("UTF-8"); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + ErrorResponse errorResponse = ErrorResponse.of(errorCode); + + try { + response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); + } catch (IOException e){ + log.error("IOException occurred while writing error response", e); + } + } + +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/RequestCachingFilter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/RequestCachingFilter.java new file mode 100644 index 00000000..cb6a4063 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/RequestCachingFilter.java @@ -0,0 +1,20 @@ +package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +public class RequestCachingFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + CachedBodyHttpServletWrapper cachedBodyHttpServletWrapper = new CachedBodyHttpServletWrapper(request); + filterChain.doFilter(cachedBodyHttpServletWrapper, response); + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java index d24ec1d4..c1e0da80 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java @@ -3,7 +3,9 @@ import kr.co.morandi.backend.member_management.domain.model.member.Member; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface MemberRepository extends JpaRepository { Boolean existsByNickname(String nickname); - + Optional findByEmail(String email); } diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java index 8ca8e81c..cf4d8bb0 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java @@ -2,10 +2,9 @@ import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; -import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; -import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import org.junit.jupiter.api.DisplayName; @@ -22,7 +21,6 @@ import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.CPP; -import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -183,7 +181,7 @@ void startSession() { } private Member createMember() { - return Member.create("nickname", "email", GOOGLE, "imageURL", "description"); + return Member.create("nickname", "email", SocialType.GOOGLE, "imageURL", "description"); } private DailyDefense createDailyDefense(LocalDate createdDate) { AtomicLong problemNumber = new AtomicLong(1L); diff --git a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java index 83daf72f..1b2db030 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java @@ -5,6 +5,7 @@ import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.SessionDetailRepository; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; @@ -18,7 +19,6 @@ import java.util.Set; import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; -import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest @@ -70,7 +70,7 @@ void findDailyDefenseSession() { private Member createMember() { - return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + return memberRepository.save(Member.create("test", "test" + "@gmail.com", SocialType.GOOGLE, "test", "test")); } } \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/member_management/domain/model/member/MemberTest.java b/src/test/java/kr/co/morandi/backend/member_management/domain/model/member/MemberTest.java index 08f26db2..3862e479 100644 --- a/src/test/java/kr/co/morandi/backend/member_management/domain/model/member/MemberTest.java +++ b/src/test/java/kr/co/morandi/backend/member_management/domain/model/member/MemberTest.java @@ -1,7 +1,5 @@ package kr.co.morandi.backend.member_management.domain.model.member; -import kr.co.morandi.backend.member_management.domain.model.member.Member; -import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java index bcc0beb7..1a637980 100644 --- a/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java @@ -1,7 +1,7 @@ package kr.co.morandi.backend.member_management.infrastructure.persistence.member; import kr.co.morandi.backend.member_management.domain.model.member.Member; -import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -9,8 +9,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.test.context.ActiveProfiles; - -import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -31,7 +29,7 @@ void tearDown() { void existsByNickname() { // given String nickname = "test"; - Member member = Member.create(nickname, "test@test.com", GOOGLE, "testImageUrl", "testDescription"); + Member member = Member.create(nickname, "test@test.com", SocialType.GOOGLE, "testImageUrl", "testDescription"); memberRepository.save(member); // when @@ -58,10 +56,10 @@ void whenNicknameNotExists() { void test() { // given String nickname = "test"; - Member originMember = Member.create(nickname, "test@test.com", GOOGLE, "testImageUrl", "testDescription"); + Member originMember = Member.create(nickname, "test@test.com", SocialType.GOOGLE, "testImageUrl", "testDescription"); memberRepository.save(originMember); - Member newMember = Member.create(nickname, "test2@test.com", GOOGLE, "testImageUrl", "testDescription"); + Member newMember = Member.create(nickname, "test2@test.com", SocialType.GOOGLE, "testImageUrl", "testDescription"); // when & then assertThatThrownBy(() -> memberRepository.save(newMember)) .isInstanceOf(DataIntegrityViolationException.class); From 9c31e952662780bac6312c1519eac6d1c1ecd31a Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Sun, 5 May 2024 15:48:38 +0900 Subject: [PATCH 11/14] =?UTF-8?q?DailyDefense=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=EC=8B=9C?= =?UTF-8?q?=ED=97=98=20=ED=83=80=EC=9D=B4=EB=A8=B8=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20(#36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: solved problemnumber set 반환 * feat: 사용자 로그인 여부에 따라 오늘의 문제 정보를 반환하는 service 및 테스트 개발 * feat: 오늘의 문제 시험 정보, 랭킹 정보 페이지로 반환하는 서비스 * fix: RankUseCaseImpl 패키지 이동 및 AtomicLong long 캐스팅 * test: DailyDefenseInfoResponse Test 추가 * refactor: DTO 생성 로직 Mapper로 분리 * feat: 오늘의 문제 정보 반환, 오늘의 문제 랭킹 Controller 및 테스트 작성 * :art: Mapper 기본 생성자 private 변경 * :art: 필요없는 중괄호 삭제 * :fire: Remove unused import 'StartDailyDefenseResponse' * :art: Dailydefense mapper 생성 * fix: @JsonInclude 어노테이션 필드로 이동 * fix: dailydefense 조회 시 problem fecth join * :sparkles: DailyDefense 시작 시 문제 content 포함 dto 변경 * :sparkles: 시험 시작 시 문제 본문 가져오게 구현, 테스트코드 작성 * fix: 응용서비스에서 Port 이용하게 변경 * :bug: WebClient 정상적으로 mocking되도록 변경 * :fire: Remove unused import * :fire: 사용하지 않는 throws 제거 * :sparkles: Webclient retryWhen을 통해 재처리 로직 추가 * :recycle: Problem Content 책임에 따라 problem_information하위로 이동 * :zap: TempCode hashmap enummap으로 변경 * :fire: Remove unused import * :art: 기본생성자 private으로 변경 * Google OAuth 적용 및 커스텀 예외 코드 추가 (#37) * :sparkles: Google OAuth 적용 및 커스텀 예외 추가 * :pencil2: 패키지 구조 일부 변경 * :pencil2: 출력 관련 코드 제거 * :pencil2: MemberAdapter 코드 수정 * :pencil2: OAuth Login과 관련한 Cookie 발급과 관련한 작업을 Service 단에서 처리하도록 수정 * :pencil2: OAuth AccessToken 발급과 관련한 메서드 분리 * :pencil2: GoogleUserDto에 @Setter, @AllArgsConstructor 제거 * :pencil2: SetDomain 관련 url, path를 yml에 저장하여 관리 * :pencil2: ErrorCode 관련 코드 수정 및 RestControllerAdvice 코드 수정 * :pencil2: OAuth domain 관련 패키지 구조 변경 * :pencil2: LoginUseCase를 목적에 맞게 AuthenticationUseCase로 변경 (인증과 관련한 작업) * :pencil2: JwtToken 및 publicKey와 PrivateKey를 발급하는 코드 수정 * :pencil2: LoginMember에서 Repository를 호출하는 형태 변경 및 패키지 구조 일부 변경 * :pencil2: Filter를 통과하는 url 리스트를 정규 표현식으로 검사하도록 수정 * :pencil2: Spring Security Filter와 관련한 코드 수정 * :pencil2: CORS 설정을 시큐리티 기본값에서 직접 정의한 내용으로 수정 * :sparkles: RefreshToken 관리를 위한 Redis 환경 구성 * :sparkles: RefreshToken을 검증하여 accessToken을 재발급하도록 코드 추가 * :pencil2: Redis에 저장하는 refreshToken에 대한 key 값을 명확하게 하기 위해 코드 수정 * :pencil2: RefreshToken을 Redis에 저장하는 로직 수정 * :pencil2: OAuth 정보와 관련된 DTO를 OAuthUserInfo, GoogleOAuthUserInfo 형태로 변경 * :pencil2: RefreshToken을 구하는 로직 수정 * :pencil2: JwtAuthenticationFilter 구조 변경 (jwtProvider, authenticationProvider, isIgnoredURIManager로 분리) * :pencil2: JwtAuthenticationFilter에서 else if를 if로 수정 * :pencil2: AccessToken, RefreshToken을 검사하는 필터에서 주석 추가 * :pencil2: Cookie 발급 로직을 CookieUtils에서 처리하도록 수정 * :pencil2: Security, OAuth 부분 패키지 구조 변경 * :pencil2: ErrorCode 및 일부 패키지 구조 수정 * :pencil2: 사용하지 않는 ErrorCode 삭제 * :pencil2: RestControllerAdvice에서 쿠키를 받는 로직을 cookieUtils 를 사용하도록 수정 * :pencil2: OAuth 관련 패키지 구조 일부 수정 * :pencil2: Jwt 검증과 관련한 메서드를 JwtValidator로 분리 * :pencil2: Securty, Jwt, OAuth 관련 패키지 분리 * :pencil2: Domain security 관련 패키지를 Application로 이동 * :pencil2: 패키지 구조 일부 변경 * :art: RefreshToken 오류 수정 및 임시 coverage 하향 * :art: Google oauth 예외처리 * :bug: Google oauth 예외처리 --------- Co-authored-by: miiiinju1 * :sparkles: 시험 시작 시 문제 본문 가져오게 구현, 테스트코드 작성 * :white_check_mark: Spring Security & WebMvcTest 충돌 해결 * :fire: 충돌 해결 * :fire: Remove unused import * :sparkles: HandlerMethodArgumentResolver 추가 및 controller 반영 * :sparkles: SetAuthentication 로직 변경 * :art: GetDailydefenseInfo 로직 변경 * :art: 로그인 여부 관계없는 API로직 filter 반영 * :sparkles: 제한 시간 후 시험 자동 종료 로직 추가 * :art: 도메인 서비스 구조 변경 * :art: ConcurrentHashMap 대신 구조적으로 타이머 등록 1회 보장 * :art: 롤백시 타이머 정상 제거를 위해 Timer Event 발행 방식 선택 * :white_check_mark: 롤백 테스트 추가 * :sparkles: RestDocs 추가 및 docstest 작성 * :memo: 오늘의 문제 랭킹 restdocs 추가 * :fire: Remove unused import * :fire: Remove unused import * :white_check_mark: 테스트 환경 통합 * :art: DailyDefenseProblemStrategy로 이름 변경 --------- Co-authored-by: Jeong Yong Choi <51875177+aj4941@users.noreply.github.com> --- build.gradle | 53 ++++- .../api/dailydefense/dailydefense.adoc | 28 +++ src/docs/asciidoc/index.adoc | 16 ++ .../common/config/WebClientConfig.java | 1 + .../backend/common/config/WebMvcConfig.java | 24 ++ .../common/exception/MorandiException.java | 3 + .../handler/GlobalExceptionHandler.java | 12 +- .../morandi/backend/common/web/MemberId.java | 11 + .../MemberHandlerMethodArgumentResolver.java | 22 ++ .../response/DailyDefenseInfoResponse.java | 27 +++ .../DailyDefenseProblemInfoResponse.java | 35 +++ .../dailydefense/DailyDefenseInfoMapper.java | 32 +++ .../DailyDefenseProblemInfoMapper.java | 37 ++++ .../port/in/DailyDefenseUseCase.java | 9 + .../dailydefense/DailyDefenseProblemPort.java | 4 + .../service/DailyDefenseUseCaseImpl.java | 57 +++++ .../domain/model/defense/Defense.java | 3 + ....java => DailyDefenseProblemStrategy.java} | 9 +- .../DailyDefenseProblemAdapter.java | 11 +- .../controller/DailyDefenseController.java | 25 +++ .../dailydefense/DailyDefenseRepository.java | 1 + .../defenseproblem/DefenseProblemMapper.java | 49 ++++ .../session/StartDailyDefenseMapper.java | 31 +++ .../mapper/tempcode/TempCodeMapper.java | 42 ++++ .../port/out/session/DefenseSessionPort.java | 1 + .../session/DefenseProblemResponse.java | 52 ++--- .../session/StartDailyDefenseResponse.java | 34 +++ .../StartDailyDefenseServiceResponse.java | 52 ----- .../response/tempcode/TempCodeResponse.java | 21 ++ .../DailyDefenseManagementService.java | 172 ++++++++------ .../service/timer/DefenseTimerService.java | 29 +++ .../domain/error/SessionErrorCode.java | 26 +++ .../domain/event/DefenseStartTimerEvent.java | 16 ++ .../domain/model/session/DefenseSession.java | 18 ++ .../domain/model/session/SessionDetail.java | 5 +- .../domain/service/DefenseEventService.java | 20 ++ .../domain/service/SessionService.java | 36 +++ .../session/DefenseSessionAdapter.java | 5 + .../DefenseMangementController.java | 29 +++ .../dto/DailyDefenseRankPageResponse.java | 31 +++ .../dto/DailyDetailRankResponse.java | 36 +++ .../dto/DailyRecordRankResponse.java | 43 ++++ .../port/in/DailyRecordRankUseCase.java | 11 + .../port/out/dailyrecord/DailyRecordPort.java | 7 +- .../port/out/record/RecordPort.java | 10 + .../service/DailyRecordRankUseCaseImpl.java | 56 +++++ .../application/util/TimeFormatHelper.java | 17 ++ .../domain/error/RecordErrorCode.java | 26 +++ .../customdefense_record/CustomRecord.java | 3 +- .../dailydefense_record/DailyDetail.java | 1 + .../dailydefense_record/DailyRecord.java | 26 +++ .../randomdefense_record/RandomRecord.java | 4 +- .../domain/model/record/Detail.java | 13 ++ .../domain/model/record/Record.java | 21 ++ .../domain/model/record/RecordStatus.java | 5 + .../stagedefense_record/StageDetail.java | 3 +- .../stagedefense_record/StageRecord.java | 4 +- .../DailyRecordAdapter.java | 13 +- .../adapter/record/RecordAdapter.java | 25 +++ .../controller/DailyRecordController.java | 26 +++ .../DailyRecordRepository.java | 15 +- .../persistence/record/RecordRepository.java | 7 + .../config/jwt/utils/JwtProvider.java | 12 +- .../config/security/SecurityConfig.java | 20 +- .../utils/AuthenticationProvider.java | 13 +- .../security/utils/IgnoredURIManager.java | 3 +- .../config/security/utils/SecurityUtils.java | 33 ++- .../exception/OAuthErrorCode.java | 2 +- .../JwtAuthenticationEntryPoint.java | 59 +++++ .../oauth/CachedBodyHttpServletWrapper.java | 5 +- .../filter/oauth/JwtAuthenticationFilter.java | 50 +++-- .../filter/oauth/JwtExceptionFilter.java | 19 +- .../filter/oauth/RequestCachingFilter.java | 2 +- .../problemcontent/ProblemContentPort.java | 11 + .../problemcontent/ProblemContent.java | 48 ++++ .../response/problemcontent/SampleData.java | 22 ++ .../response/problemcontent/Subtask.java | 23 ++ .../problemcontent/ProblemContentAdapter.java | 70 ++++++ .../backend/ControllerTestSupport.java | 44 ++++ .../backend/IntegrationTestSupport.java | 9 + .../backend/NewMorandiApplicationTests.java | 4 +- .../DailyDefenseInfoMapperTest.java | 107 +++++++++ .../service/DailyDefenseUseCaseImplTest.java | 138 ++++++++++++ .../DailyDefenseGenerationServiceTest.java | 10 +- .../defense/ProblemGenerationServiceTest.java | 5 +- .../DailyDefenseProblemAdapterTest.java | 10 +- .../DailyDefenseControllerTest.java | 45 ++++ .../algorithm/AlgorithmRepositoryTest.java | 8 +- .../CustomDefenseRepositoryTest.java | 11 +- .../DailyDefenseProblemRepositoryTest.java | 11 +- .../RandomDefenseRepositoryTest.java | 10 +- .../mapper/tempcode/TempCodeMapperTest.java | 67 ++++++ .../out/session/DefenseSessionPortTest.java | 14 +- .../DailyDefenseManagementServiceTest.java | 143 ++++++++++-- .../model/session/DefenseSessionTest.java | 45 +++- .../service/DefenseEventServiceTest.java | 71 ++++++ .../domain/service/SessionServiceTest.java | 209 ++++++++++++++++++ .../session/DefenseSessionAdapterTest.java | 9 +- .../DefenseMangementControllerTest.java | 10 + .../DailyRecordRankUseCaseTest.java | 129 +++++++++++ .../util/TimeFormatHelperTest.java | 27 +++ .../dailydefense_record/DailyRecordTest.java | 174 +++++++++++++-- .../DailyRecordAdapterTest.java | 17 +- .../adapter/record/RecordAdapterTest.java | 132 +++++++++++ .../controller/DailyRecordControllerTest.java | 51 +++++ .../DailyRecordRepositoryTest.java | 80 +++++-- .../morandi/backend/docs/RestDocsSupport.java | 33 +++ .../DailyDefenseControllerDocsTest.java | 96 ++++++++ .../DailyRecordControllerDocsTest.java | 140 ++++++++++++ .../DefenseManagementControllerDocsTest.java | 209 ++++++++++++++++++ .../member/MemberRepositoryTest.java | 8 +- .../adapter/problem/ProblemAdapterTest.java | 6 +- .../ProblemContentAdapterTest.java | 133 +++++++++++ .../config/AlgorithmInitializerTest.java | 7 +- .../problem/ProblemRepositoryTest.java | 8 +- .../request-fields.snippet | 14 ++ .../response-fields.snippet | 14 ++ 117 files changed, 3700 insertions(+), 411 deletions(-) create mode 100644 src/docs/asciidoc/api/dailydefense/dailydefense.adoc create mode 100644 src/docs/asciidoc/index.adoc create mode 100644 src/main/java/kr/co/morandi/backend/common/config/WebMvcConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/common/web/MemberId.java create mode 100644 src/main/java/kr/co/morandi/backend/common/web/resolver/MemberHandlerMethodArgumentResolver.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseInfoResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseProblemInfoResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapper.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseProblemInfoMapper.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/port/in/DailyDefenseUseCase.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImpl.java rename src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/{DailyDefenseStrategy.java => DailyDefenseProblemStrategy.java} (75%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseController.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/mapper/defenseproblem/DefenseProblemMapper.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/mapper/session/StartDailyDefenseMapper.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapper.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseResponse.java delete mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseServiceResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/response/tempcode/TempCodeResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/domain/event/DefenseStartTimerEvent.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDefenseRankPageResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDetailRankResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyRecordRankResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/application/port/in/DailyRecordRankUseCase.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/application/service/DailyRecordRankUseCaseImpl.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelper.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordStatus.java rename src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/{dailydefense => dailyrecord}/DailyRecordAdapter.java (72%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordController.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/entrypoint/JwtAuthenticationEntryPoint.java rename src/main/java/kr/co/morandi/backend/member_management/infrastructure/{ => security}/filter/oauth/CachedBodyHttpServletWrapper.java (91%) rename src/main/java/kr/co/morandi/backend/member_management/infrastructure/{ => security}/filter/oauth/JwtAuthenticationFilter.java (56%) rename src/main/java/kr/co/morandi/backend/member_management/infrastructure/{ => security}/filter/oauth/JwtExceptionFilter.java (85%) rename src/main/java/kr/co/morandi/backend/member_management/infrastructure/{ => security}/filter/oauth/RequestCachingFilter.java (89%) create mode 100644 src/main/java/kr/co/morandi/backend/problem_information/application/port/out/problemcontent/ProblemContentPort.java create mode 100644 src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/ProblemContent.java create mode 100644 src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/SampleData.java create mode 100644 src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/Subtask.java create mode 100644 src/main/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapter.java create mode 100644 src/test/java/kr/co/morandi/backend/ControllerTestSupport.java create mode 100644 src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapperTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapperTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementControllerTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_record/application/DailyRecordRankUseCaseTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelperTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordControllerTest.java create mode 100644 src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java create mode 100644 src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java create mode 100644 src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java create mode 100644 src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java create mode 100644 src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapterTest.java create mode 100644 src/test/resources/org.springframework.restdocs.templates/request-fields.snippet create mode 100644 src/test/resources/org.springframework.restdocs.templates/response-fields.snippet diff --git a/build.gradle b/build.gradle index 2e887386..41e8057c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.2.2' id 'io.spring.dependency-management' version '1.1.4' + id 'org.asciidoctor.jvm.convert' version '3.3.2' } plugins { id "org.sonarqube" version "4.4.1.3373" @@ -65,6 +66,8 @@ configurations { compileOnly { extendsFrom annotationProcessor } + + asciidoctorExt } repositories { @@ -72,32 +75,46 @@ repositories { } dependencies { + + // Spring Data Jpa implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // Spring Data Redis implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - + // Spring Security implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + + // validation + implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' + // WebFlux (WebClient) implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + // Spring Web + implementation 'org.springframework.boot:spring-boot-starter-web' + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2' + // MariaDB runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' + // lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' testImplementation 'com.h2database:h2' + + // RestDocs + asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' } tasks.named('sonarqube').configure { @@ -109,7 +126,31 @@ tasks.named('test') { finalizedBy 'jacocoTestReport' } +ext { //전역 변수 + snippetsDir = file("build/generated-snippets") +} + +test { + outputs.dir snippetsDir +} + +asciidoctor { + inputs.dir snippetsDir + configurations 'asciidoctorExt' + sources { // 특정 파일만 html로 만든다 + include '**/index.adoc' + } + baseDirFollowsSourceFile() // 다른 adoc 파일을 참조할 때 경로를 baseDir 기준으로 찾음 + dependsOn test +} + +bootJar { + dependsOn asciidoctor + from("${asciidoctor.outputDir}") { + into 'static/docs' + } +} jacocoTestReport { dependsOn test diff --git a/src/docs/asciidoc/api/dailydefense/dailydefense.adoc b/src/docs/asciidoc/api/dailydefense/dailydefense.adoc new file mode 100644 index 00000000..95ec52fa --- /dev/null +++ b/src/docs/asciidoc/api/dailydefense/dailydefense.adoc @@ -0,0 +1,28 @@ +[[Daily-Defense]] +== 오늘의 문제 정보 조회 + +=== Request +include::{snippets}/daily-defense-info/http-request.adoc[] + +=== Response +include::{snippets}/daily-defense-info/http-response.adoc[] +include::{snippets}/daily-defense-info/response-fields.adoc[] + +== 오늘의 문제 기록 조회 + +=== Request +include::{snippets}/daily-defense-ranking/http-request.adoc[] + +=== Response +include::{snippets}/daily-defense-ranking/http-response.adoc[] +include::{snippets}/daily-defense-ranking/response-fields.adoc[] + + +== 오늘의 문제 시작 + +=== Request +include::{snippets}/daily-defense-start/http-request.adoc[] + +=== Response +include::{snippets}/daily-defense-start/http-response.adoc[] +include::{snippets}/daily-defense-start/response-fields.adoc[] diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..b23195f2 --- /dev/null +++ b/src/docs/asciidoc/index.adoc @@ -0,0 +1,16 @@ +ifndef::snippets[] +:snippets: ../../build/generated-snippets +endif::[] += 모두의 랜덤 디펜스 REST API +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 1 +:sectlinks: + +include::api/dailydefense/dailydefense.adoc[] + + + +[[Daily-Defense-List]] \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java b/src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java index c06228be..2760f0df 100644 --- a/src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java +++ b/src/main/java/kr/co/morandi/backend/common/config/WebClientConfig.java @@ -6,6 +6,7 @@ @Configuration public class WebClientConfig { + @Bean public WebClient webClient() { return WebClient.builder().build(); diff --git a/src/main/java/kr/co/morandi/backend/common/config/WebMvcConfig.java b/src/main/java/kr/co/morandi/backend/common/config/WebMvcConfig.java new file mode 100644 index 00000000..3c5f8756 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/config/WebMvcConfig.java @@ -0,0 +1,24 @@ +package kr.co.morandi.backend.common.config; + +import kr.co.morandi.backend.common.web.resolver.MemberHandlerMethodArgumentResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Bean + MemberHandlerMethodArgumentResolver memberHandlerMethodArgumentResolver() { + return new MemberHandlerMethodArgumentResolver(); + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(memberHandlerMethodArgumentResolver()); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java b/src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java index 66dec4f0..dc6e3c69 100644 --- a/src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java +++ b/src/main/java/kr/co/morandi/backend/common/exception/MorandiException.java @@ -8,9 +8,12 @@ public class MorandiException extends RuntimeException { private final ErrorCode errorCode; public MorandiException(ErrorCode errorCode) { + super(errorCode.getMessage()); this.errorCode = errorCode; } + public MorandiException(ErrorCode errorCode, String message) { + super(message); this.errorCode = errorCode; } } diff --git a/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java b/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java index 3efd9cfc..dec59955 100644 --- a/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java @@ -7,17 +7,13 @@ import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import java.net.URI; - import static kr.co.morandi.backend.common.exception.handler.exception.CommonErrorCode.INTERNAL_SERVER_ERROR; import static kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType.REFRESH_TOKEN; @@ -28,9 +24,6 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { private final CookieUtils cookieUtils; - @Value("${oauth2.signup-url}") - private String signupPath; - @ExceptionHandler(MorandiException.class) public ResponseEntity morandiExceptionHandler(MorandiException e) { log.error(e.getErrorCode().name()+" : ", e.getErrorCode().getMessage() + " : ", e); @@ -39,8 +32,7 @@ public ResponseEntity morandiExceptionHandler(MorandiException e) if (e.getErrorCode().getHttpStatus() == HttpStatus.UNAUTHORIZED) { HttpHeaders headers = createUnauthorizedHeaders(); - // 로그인 페이지로 리다이렉트 - return new ResponseEntity<>(headers, HttpStatus.FOUND); + return new ResponseEntity<>(headers, HttpStatus.UNAUTHORIZED); } // 그 외의 에러가 발생한 경우 @@ -61,11 +53,9 @@ public ResponseEntity handleAllException(Exception e) { /** * Unauthorized 에러가 발생한 경우 * Refresh Token 쿠키를 제거하고 - * 로그인 페이지로 리다이렉트 */ private HttpHeaders createUnauthorizedHeaders() { HttpHeaders headers = new HttpHeaders(); - headers.setLocation(URI.create(signupPath)); Cookie cookie = cookieUtils.removeCookie(REFRESH_TOKEN, null); headers.add("Set-Cookie", cookie.toString()); return headers; diff --git a/src/main/java/kr/co/morandi/backend/common/web/MemberId.java b/src/main/java/kr/co/morandi/backend/common/web/MemberId.java new file mode 100644 index 00000000..c1858536 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/web/MemberId.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.common.web; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface MemberId { +} diff --git a/src/main/java/kr/co/morandi/backend/common/web/resolver/MemberHandlerMethodArgumentResolver.java b/src/main/java/kr/co/morandi/backend/common/web/resolver/MemberHandlerMethodArgumentResolver.java new file mode 100644 index 00000000..c5891b03 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/web/resolver/MemberHandlerMethodArgumentResolver.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.common.web.resolver; + +import kr.co.morandi.backend.common.web.MemberId; +import kr.co.morandi.backend.member_management.infrastructure.config.security.utils.SecurityUtils; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +public class MemberHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterAnnotation(MemberId.class) != null + && parameter.getParameterType().equals(Long.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return SecurityUtils.getCurrentMemberId(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseInfoResponse.java b/src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseInfoResponse.java new file mode 100644 index 00000000..8c72a786 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseInfoResponse.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.defense_information.application.dto.response; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DailyDefenseInfoResponse { + + private String defenseName; + private Integer problemCount; + private Long attemptCount; + private List problems; + + + @Builder + private DailyDefenseInfoResponse(String defenseName, Integer problemCount, Long attemptCount, List problems) { + this.defenseName = defenseName; + this.problemCount = problemCount; + this.attemptCount = attemptCount; + this.problems = problems; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseProblemInfoResponse.java b/src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseProblemInfoResponse.java new file mode 100644 index 00000000..37927f6a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/dto/response/DailyDefenseProblemInfoResponse.java @@ -0,0 +1,35 @@ +package kr.co.morandi.backend.defense_information.application.dto.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class DailyDefenseProblemInfoResponse { + + private Long problemNumber; + private Long problemId; + private Long baekjoonProblemId; + private ProblemTier difficulty; + private Long solvedCount; + private Long submitCount; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Boolean isSolved; + + @Builder + private DailyDefenseProblemInfoResponse(Long problemNumber, Long problemId, Long baekjoonProblemId, ProblemTier difficulty, Long solvedCount, Long submitCount, Boolean isSolved) { + this.problemNumber = problemNumber; + this.problemId = problemId; + this.baekjoonProblemId = baekjoonProblemId; + this.difficulty = difficulty; + this.solvedCount = solvedCount; + this.submitCount = submitCount; + this.isSolved = isSolved; + } +} + + diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapper.java b/src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapper.java new file mode 100644 index 00000000..ff38cd66 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapper.java @@ -0,0 +1,32 @@ +package kr.co.morandi.backend.defense_information.application.mapper.dailydefense; + +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DailyDefenseInfoMapper { + + public static DailyDefenseInfoResponse fromNonAttempted(DailyDefense dailyDefense) { + return DailyDefenseInfoResponse.builder() + .defenseName(dailyDefense.getContentName()) + .problemCount(dailyDefense.getProblemCount()) + .attemptCount(dailyDefense.getAttemptCount()) + .problems(DailyDefenseProblemInfoMapper.ofNonAttempted(dailyDefense.getDailyDefenseProblems())) + .build(); + } + + public static DailyDefenseInfoResponse ofAttempted(DailyDefense dailyDefense, DailyRecord dailyRecord) { + return DailyDefenseInfoResponse.builder() + .defenseName(dailyDefense.getContentName()) + .problemCount(dailyDefense.getProblemCount()) + .attemptCount(dailyDefense.getAttemptCount()) + .problems(DailyDefenseProblemInfoMapper.ofAttempted( + dailyDefense.getDailyDefenseProblems(), + dailyRecord.getSolvedProblemNumbers()) + ) + .build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseProblemInfoMapper.java b/src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseProblemInfoMapper.java new file mode 100644 index 00000000..fec9293a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseProblemInfoMapper.java @@ -0,0 +1,37 @@ +package kr.co.morandi.backend.defense_information.application.mapper.dailydefense; + +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseProblemInfoResponse; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Set; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DailyDefenseProblemInfoMapper { + + public static List ofNonAttempted(List dailyDefenseProblems) { + return dailyDefenseProblems.stream().map(problem -> DailyDefenseProblemInfoResponse.builder() + .problemNumber(problem.getProblemNumber()) + .problemId(problem.getProblem().getProblemId()) + .baekjoonProblemId(problem.getProblem().getBaekjoonProblemId()) + .difficulty(problem.getProblem().getProblemTier()) + .solvedCount(problem.getSolvedCount()) + .submitCount(problem.getSubmitCount()) + .build() + ).toList(); + } + public static List ofAttempted(List dailyDefenseProblems, Set solvedProblemNumbers) { + return dailyDefenseProblems.stream().map(problem -> DailyDefenseProblemInfoResponse.builder() + .problemNumber(problem.getProblemNumber()) + .problemId(problem.getProblem().getProblemId()) + .baekjoonProblemId(problem.getProblem().getBaekjoonProblemId()) + .difficulty(problem.getProblem().getProblemTier()) + .solvedCount(problem.getSolvedCount()) + .submitCount(problem.getSubmitCount()) + .isSolved(solvedProblemNumbers.contains(problem.getProblemNumber())) + .build() + ).toList(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/port/in/DailyDefenseUseCase.java b/src/main/java/kr/co/morandi/backend/defense_information/application/port/in/DailyDefenseUseCase.java new file mode 100644 index 00000000..da2cd7b1 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/port/in/DailyDefenseUseCase.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend.defense_information.application.port.in; + +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; + +import java.time.LocalDateTime; + +public interface DailyDefenseUseCase { + DailyDefenseInfoResponse getDailyDefenseInfo(Long memberId, LocalDateTime requestDateTime); +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java b/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java index bc14f190..d05ffef0 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/port/out/dailydefense/DailyDefenseProblemPort.java @@ -1,11 +1,15 @@ package kr.co.morandi.backend.defense_information.application.port.out.dailydefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import java.util.List; import java.util.Map; public interface DailyDefenseProblemPort { Map getDailyDefenseProblem(Map criteria); + + List findAllProblemsContainsDefenseId(Long defenseId); } diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImpl.java b/src/main/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImpl.java new file mode 100644 index 00000000..5225abe4 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImpl.java @@ -0,0 +1,57 @@ +package kr.co.morandi.backend.defense_information.application.service; + +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; +import kr.co.morandi.backend.defense_information.application.mapper.dailydefense.DailyDefenseInfoMapper; +import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class DailyDefenseUseCaseImpl implements DailyDefenseUseCase { + + private final MemberPort memberPort; + private final DailyDefensePort dailyDefensePort; + private final DailyRecordPort dailyRecordPort; + + @Override + public DailyDefenseInfoResponse getDailyDefenseInfo(Long memberId, LocalDateTime requestDateTime) { + final DailyDefense dailyDefense = dailyDefensePort.findDailyDefense(DAILY, requestDateTime.toLocalDate()); + /* + * 비로그인 상태인 경우 + * */ + if(memberId == null) { + return DailyDefenseInfoMapper.fromNonAttempted(dailyDefense); + } + /* + * 로그인 상태인 경우 + * */ + final Member member = memberPort.findMemberById(memberId); + Optional maybeDailyRecord = dailyRecordPort.findDailyRecord(member, requestDateTime.toLocalDate()); + + /* + * 시험 기록이 존재하는 경우 + * */ + if(maybeDailyRecord.isPresent()) { + DailyRecord dailyRecord = maybeDailyRecord.get(); + return DailyDefenseInfoMapper.ofAttempted(dailyDefense, dailyRecord); + } + /* + * 시험 응시 기록이 없는 경우 + * */ + return DailyDefenseInfoMapper.fromNonAttempted(dailyDefense); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java index d0e170fb..7e2ee6db 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java @@ -30,6 +30,9 @@ public abstract class Defense extends BaseEntity { @Enumerated(EnumType.STRING) private DefenseType defenseType; + public void increaseAttemptCount() { + ++this.attemptCount; + } public abstract LocalDateTime getEndTime(LocalDateTime startTime); //팩토리 메소드 패턴 public Map getDefenseProblems(ProblemGenerationService problemGenerationService) { diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseStrategy.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseProblemStrategy.java similarity index 75% rename from src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseStrategy.java rename to src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseProblemStrategy.java index b366f089..07b3fed5 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseStrategy.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseProblemStrategy.java @@ -1,10 +1,10 @@ package kr.co.morandi.backend.defense_information.domain.service.dailydefense; +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefenseProblemPort; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; import kr.co.morandi.backend.defense_information.domain.model.problem_generation_strategy.ProblemGenerationStrategy; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -17,13 +17,12 @@ @Component @RequiredArgsConstructor -public class DailyDefenseStrategy implements ProblemGenerationStrategy { +public class DailyDefenseProblemStrategy implements ProblemGenerationStrategy { - //TODO 여기도 Port로 바꿔야함 - private final DailyDefenseProblemRepository dailyDefenseProblemRepository; + private final DailyDefenseProblemPort dailyDefenseProblemPort; @Override public Map generateDefenseProblems(Defense defense) { - final List defenseProblems = dailyDefenseProblemRepository.findAllProblemsContainsDefenseId(defense.getDefenseId()); + final List defenseProblems = dailyDefenseProblemPort.findAllProblemsContainsDefenseId(defense.getDefenseId()); return defenseProblems.stream() .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); } diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapter.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapter.java index be2b09ae..1e18aef5 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapter.java @@ -1,8 +1,10 @@ package kr.co.morandi.backend.defense_information.infrastructure.adapter.dailydefense; import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefenseProblemPort; -import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import lombok.RequiredArgsConstructor; @@ -20,6 +22,8 @@ public class DailyDefenseProblemAdapter implements DailyDefenseProblemPort { private final ProblemRepository problemRepository; + private final DailyDefenseProblemRepository dailyDefenseProblemRepository; + @Override public Map getDailyDefenseProblem(Map criteria) { @@ -42,5 +46,10 @@ public Map getDailyDefenseProblem(Map crite }) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + + @Override + public List findAllProblemsContainsDefenseId(Long defenseId) { + return dailyDefenseProblemRepository.findAllProblemsContainsDefenseId(defenseId); + } } diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseController.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseController.java new file mode 100644 index 00000000..bd57233c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseController.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.defense_information.infrastructure.controller; + +import kr.co.morandi.backend.common.web.MemberId; +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; +import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; + +@RestController +@RequiredArgsConstructor +public class DailyDefenseController { + + private final DailyDefenseUseCase dailyDefenseUseCase; + + @GetMapping("/daily-defense") + public DailyDefenseInfoResponse getDailyDefenseInfo(@MemberId Long memberId) { + + return dailyDefenseUseCase.getDailyDefenseInfo(memberId, LocalDateTime.now()); + } + + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseRepository.java b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseRepository.java index 9342bfae..9cce7ceb 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseRepository.java @@ -14,6 +14,7 @@ public interface DailyDefenseRepository extends JpaRepository of(Map tryProblem, + DefenseSession defenseSession, + DailyRecord dailyRecord, + Map problemContents) { + return tryProblem.entrySet().stream() + .map(entry -> { + final Long problemNumber = entry.getKey(); + final Problem problem = entry.getValue(); + final boolean isCorrect = dailyRecord.isSolvedProblem(problemNumber); + + final SessionDetail sessionDetail = defenseSession.getSessionDetail(problemNumber); + + final Language lastAccessLanguage = sessionDetail.getLastAccessLanguage(); + final Set tempCodeResponses = TempCodeMapper.createTempCodeResponses(sessionDetail.getTempCodes()); + + return DefenseProblemResponse.builder() + .problemId(problem.getProblemId()) + .baekjoonProblemId(problem.getBaekjoonProblemId()) + .problemNumber(problemNumber) + .isCorrect(isCorrect) + .lastAccessLanguage(lastAccessLanguage) + .content(problemContents.get(problem.getBaekjoonProblemId())) + .tempCodes(tempCodeResponses) + .build(); + }) + .toList(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/mapper/session/StartDailyDefenseMapper.java b/src/main/java/kr/co/morandi/backend/defense_management/application/mapper/session/StartDailyDefenseMapper.java new file mode 100644 index 00000000..59385a72 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/mapper/session/StartDailyDefenseMapper.java @@ -0,0 +1,31 @@ +package kr.co.morandi.backend.defense_management.application.mapper.session; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_management.application.mapper.defenseproblem.DefenseProblemMapper; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class StartDailyDefenseMapper { + + public static StartDailyDefenseResponse of(Map tryProblem, + DailyDefense dailyDefense, + DefenseSession defenseSession, + DailyRecord dailyRecord, + Map problemContents) { + return StartDailyDefenseResponse.builder() + .defenseSessionId(defenseSession.getDefenseSessionId()) + .contentName(dailyDefense.getContentName()) + .defenseType(dailyDefense.getDefenseType()) + .lastAccessTime(defenseSession.getLastAccessDateTime()) + .defenseProblems(DefenseProblemMapper.of(tryProblem, defenseSession, dailyRecord, problemContents)) + .build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapper.java b/src/main/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapper.java new file mode 100644 index 00000000..e9588ddf --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapper.java @@ -0,0 +1,42 @@ +package kr.co.morandi.backend.defense_management.application.mapper.tempcode; + +import kr.co.morandi.backend.defense_management.application.response.tempcode.TempCodeResponse; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.*; +import java.util.stream.Collectors; + + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TempCodeMapper { + private static final Map intialTempCodeMap = + Arrays.stream(Language.values()) + .collect(Collectors.toMap( + language -> language, + language -> TempCodeResponse.builder() + .language(language) + .code(language.getInitialCode()) + .build())); + + /* + * TempCode 전체를 한 번에 반환하기 위해 initialTempCodeMap을 이용하여 + * 수집된 TempCode들로 replace하며 TempCodeResponse를 만들어 반환한다. + * */ + public static Set createTempCodeResponses(Set tempCodes) { + + // 기본 코드를 가지고 있는 Map을 만들어서 + Map tempCodeMap = new EnumMap<>(intialTempCodeMap); + + // tempCode를 순회하면서 tempCodeMap에 해당 언어의 TempCodeResponse를 넣어준다. + tempCodes.forEach(tempCode -> tempCodeMap.replace(tempCode.getLanguage(), TempCodeResponse.builder() + .language(tempCode.getLanguage()) + .code(tempCode.getCode()) + .build())); + + return new HashSet<>(tempCodeMap.values()); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java index 949863b7..4a04b0e8 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java @@ -10,4 +10,5 @@ public interface DefenseSessionPort { DefenseSession saveDefenseSession(DefenseSession defenseSession); Optional findTodaysDailyDefenseSession(Member member, LocalDateTime now); + Optional findDefenseSessionById(Long sessionId); } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/DefenseProblemResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/DefenseProblemResponse.java index 5c2cb65d..3c8205e4 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/DefenseProblemResponse.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/DefenseProblemResponse.java @@ -1,18 +1,14 @@ package kr.co.morandi.backend.defense_management.application.response.session; -import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; -import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; +import kr.co.morandi.backend.defense_management.application.response.tempcode.TempCodeResponse; import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; -import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.List; -import java.util.Map; +import java.util.Set; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -21,43 +17,25 @@ public class DefenseProblemResponse { private Long problemId; private Long problemNumber; private Long baekjoonProblemId; + private ProblemContent content; private boolean isCorrect; - private Language tempCodeLanguage; - private String tempCode; + private Language lastAccessLanguage; + private Set tempCodes; - - public static List fromDailyDefense(Map tryProblem, DefenseSession defenseSession, DailyRecord dailyRecord) { - return tryProblem.entrySet().stream() - .map(entry -> { - final Long problemNumber = entry.getKey(); - final Problem problem = entry.getValue(); - final boolean isCorrect = dailyRecord.isSolvedProblem(problemNumber); - - final SessionDetail sessionDetail = defenseSession.getSessionDetail(problemNumber); - - final Language lastAccessLanguage = sessionDetail.getLastAccessLanguage(); - final TempCode tempCode = sessionDetail.getTempCode(lastAccessLanguage); - - return DefenseProblemResponse.builder() - .problemId(problem.getProblemId()) - .baekjoonProblemId(problem.getBaekjoonProblemId()) - .problemNumber(problemNumber) - .isCorrect(isCorrect) - .tempCode(tempCode.getCode()) - .tempCodeLanguage(lastAccessLanguage) - .build(); - - }) - .toList(); + public boolean getIsCorrect() { + return isCorrect; } + @Builder - private DefenseProblemResponse(Long problemId, Long problemNumber, Long baekjoonProblemId, boolean isCorrect, - Language tempCodeLanguage, String tempCode) { + private DefenseProblemResponse(Long problemId, Long problemNumber, Long baekjoonProblemId, + ProblemContent content, boolean isCorrect, Language lastAccessLanguage, + Set tempCodes) { this.problemId = problemId; this.problemNumber = problemNumber; this.baekjoonProblemId = baekjoonProblemId; + this.content = content; this.isCorrect = isCorrect; - this.tempCodeLanguage = tempCodeLanguage; - this.tempCode = tempCode; + this.lastAccessLanguage = lastAccessLanguage; + this.tempCodes = tempCodes; } } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseResponse.java new file mode 100644 index 00000000..1b485a72 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseResponse.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.defense_management.application.response.session; + +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class StartDailyDefenseResponse { + + private Long defenseSessionId; + private String contentName; + private DefenseType defenseType; + private LocalDateTime lastAccessTime; + private List defenseProblems; + + @Builder + private StartDailyDefenseResponse(Long defenseSessionId, + String contentName, + DefenseType defenseType, + LocalDateTime lastAccessTime, + List defenseProblems) { + this.defenseSessionId = defenseSessionId; + this.defenseType = defenseType; + this.contentName = contentName; + this.lastAccessTime = lastAccessTime; + this.defenseProblems = defenseProblems; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseServiceResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseServiceResponse.java deleted file mode 100644 index a12500d3..00000000 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/response/session/StartDailyDefenseServiceResponse.java +++ /dev/null @@ -1,52 +0,0 @@ -package kr.co.morandi.backend.defense_management.application.response.session; - -import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; -import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; -import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class StartDailyDefenseServiceResponse { - - private Long defenseSessionId; - private String contentName; - private DefenseType defenseType; - private LocalDateTime lastAccessTime; - private List defenseProblems; - - public static StartDailyDefenseServiceResponse from(Map tryProblem, - DailyDefense dailyDefense, - DefenseSession defenseSession, - DailyRecord dailyRecord) { - return StartDailyDefenseServiceResponse.builder() - .defenseSessionId(defenseSession.getDefenseSessionId()) - .contentName(dailyDefense.getContentName()) - .defenseType(dailyDefense.getDefenseType()) - .lastAccessTime(defenseSession.getLastAccessDateTime()) - .defenseProblems(DefenseProblemResponse.fromDailyDefense(tryProblem, defenseSession, dailyRecord)) - .build(); - } - - @Builder - private StartDailyDefenseServiceResponse(Long defenseSessionId, - String contentName, - DefenseType defenseType, - LocalDateTime lastAccessTime, - List defenseProblems) { - this.defenseSessionId = defenseSessionId; - this.defenseType = defenseType; - this.contentName = contentName; - this.lastAccessTime = lastAccessTime; - this.defenseProblems = defenseProblems; - } -} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/response/tempcode/TempCodeResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/tempcode/TempCodeResponse.java new file mode 100644 index 00000000..eaee2872 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/response/tempcode/TempCodeResponse.java @@ -0,0 +1,21 @@ +package kr.co.morandi.backend.defense_management.application.response.tempcode; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class TempCodeResponse { + + private Language language; + private String code; + + @Builder + private TempCodeResponse(Language language, String code) { + this.language = language; + this.code = code; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java index cf68f643..15455ed3 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java @@ -1,74 +1,110 @@ -package kr.co.morandi.backend.defense_management.application.service.session; - -import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; -import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; -import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; -import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; -import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; -import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; -import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseServiceResponse; -import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; -import kr.co.morandi.backend.member_management.domain.model.member.Member; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.Map; -import java.util.Optional; - -import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class DailyDefenseManagementService { - - private final DailyDefensePort dailyDefensePort; - private final DailyRecordPort dailyRecordPort; - private final ProblemGenerationService problemGenerationService; - private final DefenseSessionPort defenseSessionPort; - - @Transactional - public StartDailyDefenseServiceResponse startDailyDefense(StartDailyDefenseServiceRequest request, Member member, LocalDateTime requestTime) { - Long problemNumber = request.getProblemNumber(); - - // 세션이랑 세션 Detail을 찾아서 응시 기록이 있는지 살펴보기 - final Optional maybeDefenseSession = defenseSessionPort.findTodaysDailyDefenseSession(member, requestTime); - - // 오늘의 Defense를 찾아오기 - final DailyDefense dailyDefense = dailyDefensePort.findDailyDefense(DAILY, requestTime.toLocalDate()); - - // 오늘의 문제 목록 중에서 원하는 문제를 찾아서 시도하려는 문제 목록에 추가 (오늘의 문제 목록에 해당 문제가 없으면 예외 발생) - final Map tryProblem = dailyDefense.getTryingProblem(problemNumber, problemGenerationService); - - // DefenseSession이 있으면 get, 없으면 새로운 DefenseSession을 생성 - final DefenseSession defenseSession = maybeDefenseSession.orElseGet(() -> createNewSession(member, requestTime, dailyDefense, tryProblem)); - - // DefenseSession의 recordId로 DailyRecord를 찾고 문제를 시도했는지 확인하고 시도하지 않았으면 시도하도록 함 - Long recordId = defenseSession.getRecordId(); - DailyRecord dailyRecord = dailyRecordPort.findDailyRecord(member, recordId, requestTime.toLocalDate()) - .orElseThrow(() -> new IllegalArgumentException("세션에 해당하는 응시 기록이 없습니다.")); - - if (!defenseSession.hasTriedProblem(problemNumber)) { - dailyRecord.tryMoreProblem(tryProblem); - defenseSession.tryMoreProblem(problemNumber, requestTime); + package kr.co.morandi.backend.defense_management.application.service.session; + + import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; + import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; + import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; + import kr.co.morandi.backend.defense_management.application.mapper.session.StartDailyDefenseMapper; + import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; + import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; + import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; + import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; + import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; + import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; + import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; + import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; + import kr.co.morandi.backend.member_management.domain.model.member.Member; + import kr.co.morandi.backend.problem_information.application.port.out.problemcontent.ProblemContentPort; + import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; + import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; + import lombok.RequiredArgsConstructor; + import org.springframework.context.ApplicationEventPublisher; + import org.springframework.stereotype.Service; + import org.springframework.transaction.annotation.Transactional; + + import java.time.LocalDateTime; + import java.util.Map; + import java.util.Optional; + + import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; + + @Service + @Transactional(readOnly = true) + @RequiredArgsConstructor + public class DailyDefenseManagementService { + + private final DailyDefensePort dailyDefensePort; + private final DailyRecordPort dailyRecordPort; + private final ProblemGenerationService problemGenerationService; + private final DefenseSessionPort defenseSessionPort; + private final MemberPort memberPort; + private final ProblemContentPort problemContentPort; + private final ApplicationEventPublisher publisher; + + @Transactional + public StartDailyDefenseResponse startDailyDefense(StartDailyDefenseServiceRequest request, Long memberId, LocalDateTime requestTime) { + Long problemNumber = request.getProblemNumber(); + Member member = memberPort.findMemberById(memberId); + + // 세션이랑 세션 Detail을 찾아서 응시 기록이 있는지 살펴보기 + final Optional maybeDefenseSession = defenseSessionPort.findTodaysDailyDefenseSession(member, requestTime); + + // 오늘의 Defense를 찾아오기 + final DailyDefense dailyDefense = dailyDefensePort.findDailyDefense(DAILY, requestTime.toLocalDate()); + + // 오늘의 문제 목록 중에서 원하는 문제를 찾아서 시도하려는 문제 목록에 추가 (오늘의 문제 목록에 해당 문제가 없으면 예외 발생) + final Map tryProblem = dailyDefense.getTryingProblem(problemNumber, problemGenerationService); + + // DefenseSession이 있으면 get, 없으면 새로운 DefenseSession을 생성 + final DefenseSession defenseSession = maybeDefenseSession.orElseGet(() -> createNewSession(member, requestTime, dailyDefense, tryProblem)); + + // DefenseSession의 recordId로 DailyRecord를 찾고 문제를 시도했는지 확인하고 시도하지 않았으면 시도하도록 함 + Long recordId = defenseSession.getRecordId(); + DailyRecord dailyRecord = dailyRecordPort.findDailyRecord(member, recordId, requestTime.toLocalDate()) + .orElseThrow(() -> new IllegalArgumentException("세션에 해당하는 응시 기록이 없습니다.")); + + if (!defenseSession.hasTriedProblem(problemNumber)) { + dailyRecord.tryMoreProblem(tryProblem); + defenseSession.tryMoreProblem(problemNumber, requestTime); + } + + final DefenseSession savedDefenseSession = defenseSessionPort.saveDefenseSession(defenseSession); + + + // 문제 내용 가져오기 + final Map problemContent = getProblemContents(tryProblem); + + // 문제 목록을 DefenseProblemResponse DTO로 변환 + return StartDailyDefenseMapper.of(tryProblem, dailyDefense, savedDefenseSession, dailyRecord, problemContent); } - final DefenseSession savedDefenseSession = defenseSessionPort.saveDefenseSession(defenseSession); + /* + * 백준 문제 ID 목록을 받아서 문제 내용을 가져오는 메소드 + * */ + private Map getProblemContents(Map tryProblem) { + return problemContentPort.getProblemContents(tryProblem.values() + .stream() + .map(Problem::getBaekjoonProblemId) + .toList()); + } - // 문제 목록을 DefenseProblemResponse DTO로 변환 - return StartDailyDefenseServiceResponse.from(tryProblem, dailyDefense, savedDefenseSession, dailyRecord); - } - private DefenseSession createNewSession(Member member, LocalDateTime now, DailyDefense dailyDefense, Map tryProblem) { - DailyRecord dailyRecord = DailyRecord.tryDefense(now, dailyDefense, member, tryProblem); - DailyRecord savedDailyRecord = dailyRecordPort.saveDailyRecord(dailyRecord); - Long recordId = savedDailyRecord.getRecordId(); + /* + * 세션이 존재하지 않을 경우 새롭게 시험을 시작하는 메소드 + * */ + private DefenseSession createNewSession(Member member, LocalDateTime now, DailyDefense dailyDefense, Map tryProblem) { + DailyRecord dailyRecord = DailyRecord.tryDefense(now, dailyDefense, member, tryProblem); + DailyRecord savedDailyRecord = dailyRecordPort.saveDailyRecord(dailyRecord); + Long recordId = savedDailyRecord.getRecordId(); - return DefenseSession.startSession(member, recordId, dailyDefense.getDefenseType(), tryProblem.keySet(), now, dailyDefense.getEndTime(now)); - } + final DefenseSession defenseSession = defenseSessionPort.saveDefenseSession( + DefenseSession.startSession(member, recordId, dailyDefense.getDefenseType(), tryProblem.keySet(), now, dailyDefense.getEndTime(now))); + /* + * DefenseSession에 관련된 타이머 시작 이벤트 발행 + * */ + publisher.publishEvent(new DefenseStartTimerEvent(defenseSession.getDefenseSessionId(), defenseSession.getStartDateTime(), defenseSession.getEndDateTime())); -} + return defenseSession; + } + + + } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java new file mode 100644 index 00000000..6a7bdb4b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java @@ -0,0 +1,29 @@ + package kr.co.morandi.backend.defense_management.application.service.timer; + + import kr.co.morandi.backend.defense_management.domain.service.SessionService; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Service; + + import java.time.Duration; + import java.time.LocalDateTime; + import java.util.concurrent.Executors; + import java.util.concurrent.ScheduledExecutorService; + import java.util.concurrent.TimeUnit; + + @Service + @RequiredArgsConstructor + public class DefenseTimerService { + + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + private final SessionService sessionService; + + public void startDefenseTimer(Long defenseSessionId, LocalDateTime startDateTime, LocalDateTime endDateTime) { + + long delay = Duration.between(startDateTime, endDateTime).toMillis(); + + scheduler.schedule(() -> + sessionService.terminateDefense(defenseSessionId), delay, TimeUnit.MILLISECONDS); + + } + + } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java new file mode 100644 index 00000000..cdb6b248 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.defense_management.domain.error; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum SessionErrorCode implements ErrorCode { + SESSION_NOT_FOUND(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다."), + SESSION_ALREADY_STARTED(HttpStatus.BAD_REQUEST, "이미 시작된 세션입니다."), + SESSION_ALREADY_ENDED(HttpStatus.BAD_REQUEST, "이미 종료된 세션입니다."),; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + @Override + public String getMessage() { + return message; + } + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/event/DefenseStartTimerEvent.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/event/DefenseStartTimerEvent.java new file mode 100644 index 00000000..7b12032e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/event/DefenseStartTimerEvent.java @@ -0,0 +1,16 @@ +package kr.co.morandi.backend.defense_management.domain.event; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@RequiredArgsConstructor +public class DefenseStartTimerEvent { + + private final Long sessionId; + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java index 94b069e0..2545be6b 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java @@ -1,10 +1,14 @@ package kr.co.morandi.backend.defense_management.domain.model.session; import jakarta.persistence.*; +import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.common.model.BaseEntity; import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; +import kr.co.morandi.backend.defense_management.domain.service.SessionService; import kr.co.morandi.backend.member_management.domain.model.member.Member; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -47,6 +51,18 @@ public class DefenseSession extends BaseEntity { private static final Long INITIAL_ACCESS_PROBLEM_NUMBER = 1L; + public void terminateDefense(SessionService sessionService) { + sessionService.terminateDefense(this.getDefenseSessionId()); + } + + public boolean terminateSession() { + if(examStatus == ExamStatus.COMPLETED) { + throw new MorandiException(SessionErrorCode.SESSION_ALREADY_ENDED); + } + examStatus = ExamStatus.COMPLETED; + return true; + } + public SessionDetail getSessionDetail(Long problemNumber) { return getSessionDetails().stream() .filter(detail -> Objects.equals(detail.getProblemNumber(), problemNumber)) @@ -75,6 +91,8 @@ public static DefenseSession startSession(Member member, Long recordId, DefenseT LocalDateTime startDateTime, LocalDateTime endDateTime) { return new DefenseSession(member, recordId, defenseType, problemNumbers, startDateTime, endDateTime); } + + @Builder private DefenseSession(Member member, Long recordId, DefenseType defenseType, Set problemNumbers, LocalDateTime startDateTime, LocalDateTime endDateTime) { if(problemNumbers==null || problemNumbers.isEmpty()) diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java index 74e7351d..8c339c7e 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java @@ -9,9 +9,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; +import java.util.*; @Entity @Getter @@ -29,6 +27,7 @@ public class SessionDetail extends BaseEntity { private Long problemNumber; + @Enumerated(EnumType.STRING) private Language lastAccessLanguage; public static final Language INITIAL_LANGUAGE = Language.CPP; diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java new file mode 100644 index 00000000..ce382763 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java @@ -0,0 +1,20 @@ +package kr.co.morandi.backend.defense_management.domain.service; + +import kr.co.morandi.backend.defense_management.application.service.timer.DefenseTimerService; +import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Service +@RequiredArgsConstructor +public class DefenseEventService { + + private final DefenseTimerService defenseTimerService; + + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void onDefenseStartTimerEvent(DefenseStartTimerEvent event) { + defenseTimerService.startDefenseTimer(event.getSessionId(), event.getStartDateTime(), event.getEndDateTime()); + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java new file mode 100644 index 00000000..51fbbfde --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java @@ -0,0 +1,36 @@ +package kr.co.morandi.backend.defense_management.domain.service; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_record.application.port.out.record.RecordPort; +import kr.co.morandi.backend.defense_record.domain.error.RecordErrorCode; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class SessionService { + + private final DefenseSessionPort defenseSessionPort; + private final RecordPort recordPort; + + @Transactional + public void terminateDefense(Long sessionId) { + final DefenseSession defenseSession = defenseSessionPort.findDefenseSessionById(sessionId) + .orElseThrow(() -> new MorandiException(SessionErrorCode.SESSION_NOT_FOUND)); + + defenseSession.terminateSession(); + + final Record record = recordPort.findRecordById(defenseSession.getRecordId()) + .orElseThrow(() -> new MorandiException(RecordErrorCode.RECORD_NOT_FOUND)); + + record.terminteDefense(); + + defenseSessionPort.saveDefenseSession(defenseSession); + recordPort.saveRecord(record); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java index ae98400f..37a2af7b 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java @@ -27,4 +27,9 @@ public DefenseSession saveDefenseSession(DefenseSession defenseSession) { public Optional findTodaysDailyDefenseSession(Member member, LocalDateTime now) { return defenseSessionRepository.findDailyDefenseSession(member, DAILY, now); } + + @Override + public Optional findDefenseSessionById(Long sessionId) { + return defenseSessionRepository.findById(sessionId); + } } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java new file mode 100644 index 00000000..4f3a4612 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java @@ -0,0 +1,29 @@ +package kr.co.morandi.backend.defense_management.infrastructure.controller; + +import jakarta.validation.Valid; +import kr.co.morandi.backend.common.web.MemberId; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; +import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; +import kr.co.morandi.backend.defense_management.infrastructure.request.dailydefense.StartDailyDefenseRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; + +@RestController +@RequestMapping("/daily-defense") +@RequiredArgsConstructor +public class DefenseMangementController { + + private final DailyDefenseManagementService dailyDefenseManagementService; + + @PostMapping + public ResponseEntity startDailyDefense(@MemberId Long memberId, + @Valid @RequestBody StartDailyDefenseRequest request) { + + return ResponseEntity.ok(dailyDefenseManagementService + .startDailyDefense(request.toServiceRequest(), memberId, LocalDateTime.now()) + ); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDefenseRankPageResponse.java b/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDefenseRankPageResponse.java new file mode 100644 index 00000000..05de8aee --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDefenseRankPageResponse.java @@ -0,0 +1,31 @@ +package kr.co.morandi.backend.defense_record.application.dto; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DailyDefenseRankPageResponse { + + private List dailyRecords; + private Integer totalPage; + private Integer currentPage; + + public static DailyDefenseRankPageResponse of(List dailyRecords, Integer totalPage, Integer currentPage) { + return DailyDefenseRankPageResponse.builder() + .dailyRecords(dailyRecords) + .totalPage(totalPage) + .currentPage(currentPage) + .build(); + } + @Builder + private DailyDefenseRankPageResponse(List dailyRecords, Integer totalPage, Integer currentPage) { + this.dailyRecords = dailyRecords; + this.totalPage = totalPage; + this.currentPage = currentPage; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDetailRankResponse.java b/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDetailRankResponse.java new file mode 100644 index 00000000..4f5777c8 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyDetailRankResponse.java @@ -0,0 +1,36 @@ +package kr.co.morandi.backend.defense_record.application.dto; + +import kr.co.morandi.backend.defense_record.application.util.TimeFormatHelper; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DailyDetailRankResponse { + + private Long problemNumber; + private Boolean isSolved; + private String solvedTime; + + public static List of(List dailyDetails) { + return dailyDetails.stream() + .map(details -> DailyDetailRankResponse.builder() + .problemNumber(details.getProblemNumber()) + .isSolved(details.getIsSolved()) + .solvedTime(TimeFormatHelper.solvedTimeToString(details.getSolvedTime())) + .build()) + .toList(); + } + + @Builder + private DailyDetailRankResponse(Long problemNumber, Boolean isSolved, String solvedTime) { + this.problemNumber = problemNumber; + this.isSolved = isSolved; + this.solvedTime = solvedTime; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyRecordRankResponse.java b/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyRecordRankResponse.java new file mode 100644 index 00000000..977791d0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/dto/DailyRecordRankResponse.java @@ -0,0 +1,43 @@ +package kr.co.morandi.backend.defense_record.application.dto; + +import kr.co.morandi.backend.defense_record.application.util.TimeFormatHelper; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class DailyRecordRankResponse { + + private String nickname; + private Long rank; + private Long solvedCount; + private LocalDateTime updatedAt; + private String totalSolvedTime; + private List rankDetails; + + public static DailyRecordRankResponse of(String nickname, Long rank, LocalDateTime updatedAt, Long totalSolvedTime, Long totalSolvedCount, List rankDetails) { + return DailyRecordRankResponse.builder() + .nickname(nickname) + .rank(rank) + .updatedAt(updatedAt) + .solvedCount(totalSolvedCount) + .rankDetails(rankDetails) + .totalSolvedTime(TimeFormatHelper.solvedTimeToString(totalSolvedTime)) + .build(); + } + @Builder + private DailyRecordRankResponse(String nickname, Long rank, Long solvedCount, LocalDateTime updatedAt, String totalSolvedTime, List rankDetails) { + this.nickname = nickname; + this.rank = rank; + this.solvedCount = solvedCount; + this.updatedAt = updatedAt; + this.totalSolvedTime = totalSolvedTime; + this.rankDetails = rankDetails; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/port/in/DailyRecordRankUseCase.java b/src/main/java/kr/co/morandi/backend/defense_record/application/port/in/DailyRecordRankUseCase.java new file mode 100644 index 00000000..2d72d3f9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/port/in/DailyRecordRankUseCase.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.defense_record.application.port.in; + +import kr.co.morandi.backend.defense_record.application.dto.DailyDefenseRankPageResponse; + +import java.time.LocalDateTime; + +public interface DailyRecordRankUseCase { + + // TODO 공통 등수 로직 부분 빠짐 + DailyDefenseRankPageResponse getDailyRecordRank(LocalDateTime requestTime, int page, int size); +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/dailyrecord/DailyRecordPort.java b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/dailyrecord/DailyRecordPort.java index 46f3d845..ebdf1f39 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/dailyrecord/DailyRecordPort.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/dailyrecord/DailyRecordPort.java @@ -1,15 +1,16 @@ package kr.co.morandi.backend.defense_record.application.port.out.dailyrecord; -import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import org.springframework.data.domain.Page; import java.time.LocalDate; import java.util.Optional; public interface DailyRecordPort { + DailyRecord saveDailyRecord(DailyRecord dailyRecord); Optional findDailyRecord(Member member, LocalDate date); Optional findDailyRecord(Member member, Long recordId, LocalDate date); - - + Page findDailyRecordRank(LocalDate requestDate, Integer page, Integer size); } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java new file mode 100644 index 00000000..ab893949 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java @@ -0,0 +1,10 @@ +package kr.co.morandi.backend.defense_record.application.port.out.record; + +import kr.co.morandi.backend.defense_record.domain.model.record.Record; + +import java.util.Optional; + +public interface RecordPort { + Optional> findRecordById(Long recordId); + void saveRecord(Record record); +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/service/DailyRecordRankUseCaseImpl.java b/src/main/java/kr/co/morandi/backend/defense_record/application/service/DailyRecordRankUseCaseImpl.java new file mode 100644 index 00000000..dfd27164 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/service/DailyRecordRankUseCaseImpl.java @@ -0,0 +1,56 @@ +package kr.co.morandi.backend.defense_record.application.service; + +import kr.co.morandi.backend.defense_record.application.dto.DailyDefenseRankPageResponse; +import kr.co.morandi.backend.defense_record.application.dto.DailyDetailRankResponse; +import kr.co.morandi.backend.defense_record.application.dto.DailyRecordRankResponse; +import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class DailyRecordRankUseCaseImpl implements DailyRecordRankUseCase { + + private final DailyRecordPort dailyRecordPort; + + + // TODO 공통 등수 로직 부분 빠짐 + @Override + public DailyDefenseRankPageResponse getDailyRecordRank(LocalDateTime requestTime, int page, int size) { + final Page dailyRecords = dailyRecordPort.findDailyRecordRank(requestTime.toLocalDate(), page, size); + + // 등수 계산 + // TODO 동점자 처리 필요 + AtomicLong initialRank = new AtomicLong((long) page * size + 1); + + final List dailyRecordRanks = dailyRecords.stream() + .map(dr -> { + String member = dr.getMember().getNickname(); + Long rank = initialRank.getAndIncrement(); + List details = DailyDetailRankResponse.of(dr.getDetails()); + Long totalSolvedTime = dr.getDetails().stream() + .mapToLong(DailyDetail::getSolvedTime) + .sum(); + Long solvedCount = dr.getDetails().stream() + .filter(DailyDetail::getIsSolved) + .count(); + + return DailyRecordRankResponse.of(member, rank, requestTime, totalSolvedTime, solvedCount, details); + }) + .toList(); + + return DailyDefenseRankPageResponse.of(dailyRecordRanks, dailyRecords.getTotalPages(), page); + } + + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelper.java b/src/main/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelper.java new file mode 100644 index 00000000..6d6ea7ea --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelper.java @@ -0,0 +1,17 @@ +package kr.co.morandi.backend.defense_record.application.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TimeFormatHelper { + + public static String solvedTimeToString(Long solvedTime) { + return String.format("%02d:%02d:%02d", solvedTime / 3600, (solvedTime % 3600) / 60, solvedTime % 60); + } + + public static Long stringToSolvedTime(String solvedTime) { + String[] time = solvedTime.split(":"); + return Long.parseLong(time[0]) * 3600 + Long.parseLong(time[1]) * 60 + Long.parseLong(time[2]); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java new file mode 100644 index 00000000..da8bfd95 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.defense_record.domain.error; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum RecordErrorCode implements ErrorCode { + RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, "시험 기록(Record)를 찾을 수 없습니다."), + RECORD_ALREADY_TERMINATED(HttpStatus.BAD_REQUEST, "이미 종료된 시험 기록입니다.") + ; + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java index 03797d50..f55418b6 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java @@ -21,7 +21,7 @@ @Getter @DiscriminatorValue("CustomRecord") public class CustomRecord extends Record { - private Long totalSolvedTime; + private Integer solvedCount; private Integer problemCount; @Override @@ -30,7 +30,6 @@ public CustomDetail createDetail(Member member, Long sequenceNumber, Problem pro } private CustomRecord(CustomDefense customDefense, Member member, LocalDateTime testDate, Map problems) { super(testDate, customDefense, member, problems); - this.totalSolvedTime = 0L; this.solvedCount = 0; this.problemCount = customDefense.getProblemCount(); } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java index 418cc5de..4df3bb6a 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java @@ -21,6 +21,7 @@ public class DailyDetail extends Detail { Long problemNumber; + private DailyDetail(Member member, Long problemNumber, Problem problem, Record records, Defense defense) { super(member, problem, records, defense); this.problemNumber = problemNumber; diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java index 1bbb9b3f..ef73c7cd 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java @@ -13,6 +13,7 @@ import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; +import java.time.Duration; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -29,6 +30,27 @@ public class DailyRecord extends Record { private Long solvedCount; private Integer problemCount; + public void solveProblem(Long problemNumber, String code, LocalDateTime solvedAt) { + super.getDetails().stream() + .filter(detail -> detail.getProblemNumber().equals(problemNumber)) + .findFirst() + .ifPresent(detail -> { + + Long solvedTime = calculateSolvedTime(solvedAt); + + if(detail.solveProblem(code, solvedTime)) { + ++this.solvedCount; + super.addTotalSolvedTime(solvedTime); + } + }); + } + + public Set getSolvedProblemNumbers() { + return super.getDetails().stream() + .filter(DailyDetail::getIsSolved) + .map(DailyDetail::getProblemNumber) + .collect(Collectors.toSet()); + } public boolean isSolvedProblem(Long problemNumber) { return super.getDetails().stream() .anyMatch(detail -> detail.getProblemNumber().equals(problemNumber) @@ -71,6 +93,10 @@ private DailyRecord(LocalDateTime date, Defense defense, Member member, Map { - private Long totalSolvedTime; + private Integer solvedCount; private Integer problemCount; - private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; private static final Integer INITIAL_SOLVED_COUNT = 0; private RandomRecord(LocalDateTime testDate, RandomDefense randomDefense, Member member, Map problems) { super(testDate, randomDefense, member, problems); - this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; this.solvedCount = INITIAL_SOLVED_COUNT; this.problemCount = problems.size(); } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java index b004f524..9beaf3fc 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java @@ -39,12 +39,25 @@ public abstract class Detail extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private Problem problem; + private Long solvedTime; + private static final Long INITIAL_SUBMIT_COUNT = 0L; + private static final Long INITIAL_SOLVED_TIME = 0L; private static final Boolean INITIAL_IS_SOLVED = false; + public boolean solveProblem(String solvedCode, Long solvedTime) { + if(this.isSolved) { + return false; + } + this.isSolved = true; + this.solvedCode = solvedCode; + this.solvedTime = solvedTime; + return true; + } protected Detail(Member member, Problem problem, Record records, Defense defense) { this.isSolved = INITIAL_IS_SOLVED; this.submitCount = INITIAL_SUBMIT_COUNT; + this.solvedTime = INITIAL_SOLVED_TIME; this.solvedCode = null; this.defense = defense; this.record = records; diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java index 4a0f94f9..252676f7 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java @@ -1,8 +1,10 @@ package kr.co.morandi.backend.defense_record.domain.model.record; import jakarta.persistence.*; +import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.common.model.BaseEntity; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_record.domain.error.RecordErrorCode; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AccessLevel; @@ -40,12 +42,31 @@ public abstract class Record extends BaseEntity { @OneToMany(mappedBy = "record", cascade = CascadeType.ALL, targetEntity = Detail.class) private List details = new ArrayList<>(); + private Long totalSolvedTime; + + @Enumerated(EnumType.STRING) + private RecordStatus status; + + private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; + + public boolean terminteDefense() { + if(this.status.equals(RecordStatus.COMPLETED)) { + throw new MorandiException(RecordErrorCode.RECORD_ALREADY_TERMINATED); + } + this.status = RecordStatus.COMPLETED; + return true; + } + public void addTotalSolvedTime(Long totalSolvedTime) { + this.totalSolvedTime += totalSolvedTime; + } protected abstract T createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense); protected Record(LocalDateTime testDate, Defense defense, Member member, Map problems) { this.testDate = testDate; this.defense = defense; this.member = member; + this.status = RecordStatus.IN_PROGRESS; + this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; this.details = problems.entrySet().stream() .map(problem -> this.createDetail(member, problem.getKey(), problem.getValue(), this, defense)) .collect(Collectors.toCollection(ArrayList::new)); diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordStatus.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordStatus.java new file mode 100644 index 00000000..a344ed71 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordStatus.java @@ -0,0 +1,5 @@ +package kr.co.morandi.backend.defense_record.domain.model.record; + +public enum RecordStatus { + IN_PROGRESS, COMPLETED +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java index 3b1d3802..58d9ff9b 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java @@ -18,12 +18,11 @@ @Getter @DiscriminatorValue("StageDefenseProblemRecord") public class StageDetail extends Detail { - private Long solvedTime; + private Long stageNumber; private StageDetail(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { super(member, problem, records, defense); - this.solvedTime = 0L; this.stageNumber = stageNumber; } public static StageDetail create(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java index d173dd76..fb9eb513 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java @@ -20,16 +20,14 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("StageDefenseRecord") public class StageRecord extends Record { - private Long totalSolvedTime; + private Long stageCount; - private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; private static final Long INITIAL_STAGE_NUMBER = 1L; private static final Long INITIAL_STAGE_COUNT = 1L; private StageRecord(Defense defense, LocalDateTime testDate, Member member, Map problems) { super(testDate, defense, member, problems); - this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; this.stageCount = INITIAL_STAGE_COUNT; } @Override diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense/DailyRecordAdapter.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailyrecord/DailyRecordAdapter.java similarity index 72% rename from src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense/DailyRecordAdapter.java rename to src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailyrecord/DailyRecordAdapter.java index 13180100..2ce8a4fa 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense/DailyRecordAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailyrecord/DailyRecordAdapter.java @@ -1,10 +1,13 @@ -package kr.co.morandi.backend.defense_record.infrastructure.adapter.dailydefense; +package kr.co.morandi.backend.defense_record.infrastructure.adapter.dailyrecord; import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import java.time.LocalDate; @@ -29,4 +32,12 @@ public Optional findDailyRecord(Member member, LocalDate date) { public Optional findDailyRecord(Member member, Long recordId, LocalDate date) { return dailyRecordRepository.findDailyRecordByRecordId(member, recordId, date); } + /* + * 조회 시간 별 DailyDefense 등수 조회 + * */ + @Override + public Page findDailyRecordRank(LocalDate requestDate, Integer page, Integer size) { + Pageable pageable = PageRequest.of(page, size); + return dailyRecordRepository.getDailyRecordsRankByDate(requestDate, pageable); + } } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java new file mode 100644 index 00000000..fd48f8b3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.defense_record.infrastructure.adapter.record; + +import kr.co.morandi.backend.defense_record.application.port.out.record.RecordPort; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.record.RecordRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +@RequiredArgsConstructor +public class RecordAdapter implements RecordPort { + + private final RecordRepository recordRepository; + @Override + public Optional> findRecordById(Long recordId) { + return recordRepository.findById(recordId); + } + + @Override + public void saveRecord(Record record) { + recordRepository.save(record); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordController.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordController.java new file mode 100644 index 00000000..e1d6a157 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordController.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.defense_record.infrastructure.controller; + +import kr.co.morandi.backend.defense_record.application.dto.DailyDefenseRankPageResponse; +import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; + +@RestController +@RequiredArgsConstructor +public class DailyRecordController { + + private final DailyRecordRankUseCase dailyRecordRankUseCase; + + @GetMapping("/daily-record/rankings") + public ResponseEntity getDailyRecordRank(@RequestParam(value = "page", defaultValue = "0") int page, + @RequestParam(value = "size", defaultValue = "5") int size) { + + return ResponseEntity.ok(dailyRecordRankUseCase.getDailyRecordRank(LocalDateTime.now(), page, size)); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java index 24c5c4ea..2bb3c0af 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java @@ -1,7 +1,9 @@ package kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record; -import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +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; @@ -30,5 +32,14 @@ and CAST(dr.testDate as localdate) = :date and CAST(dr.testDate as localdate) = :date """) Optional findDailyRecordByRecordId(Member member, Long recordId, LocalDate date); - + /* + * Paging 처리이기 때문에 fetch join을 사용하지 않는다. + * */ + @Query(""" + select dr + from DailyRecord dr + where CAST(dr.testDate as localdate) = :requestDate + order by dr.solvedCount desc, dr.totalSolvedTime asc, dr.recordId asc + """) + Page getDailyRecordsRankByDate(LocalDate requestDate, Pageable pageable); } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java new file mode 100644 index 00000000..7fc61c7f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_record.infrastructure.persistence.record; + +import kr.co.morandi.backend.defense_record.domain.model.record.Record; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RecordRepository extends JpaRepository, Long> { +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java index affc3525..f3bdacea 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/jwt/utils/JwtProvider.java @@ -1,10 +1,10 @@ package kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; -import kr.co.morandi.backend.common.exception.MorandiException; -import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.domain.model.member.Role; import kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType; @@ -14,6 +14,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.util.WebUtils; + import java.security.PrivateKey; import java.util.Date; @@ -38,13 +39,12 @@ public String parseAccessToken(HttpServletRequest request) { if (StringUtils.hasText(accessToken) && accessToken.startsWith("Bearer ")) { return accessToken.substring(7); } - throw new MorandiException(OAuthErrorCode.ACCESS_TOKEN_NOT_FOUND); + return null; } public String parseRefreshToken(HttpServletRequest request) { Cookie cookie = WebUtils.getCookie(request, "REFRESH_TOKEN"); if(cookie==null) - throw new MorandiException(OAuthErrorCode.REFRESH_TOKEN_NOT_FOUND); - + return null; return cookie.getValue(); } public String reissueAccessToken(String refreshToken) { diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java index 0cbeb7a3..8b7d5b95 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/SecurityConfig.java @@ -1,21 +1,23 @@ package kr.co.morandi.backend.member_management.infrastructure.config.security; -import kr.co.morandi.backend.member_management.infrastructure.filter.oauth.JwtAuthenticationFilter; -import kr.co.morandi.backend.member_management.infrastructure.filter.oauth.JwtExceptionFilter; -import kr.co.morandi.backend.member_management.infrastructure.filter.oauth.RequestCachingFilter; +import kr.co.morandi.backend.member_management.infrastructure.security.filter.entrypoint.JwtAuthenticationEntryPoint; +import kr.co.morandi.backend.member_management.infrastructure.security.filter.oauth.JwtAuthenticationFilter; +import kr.co.morandi.backend.member_management.infrastructure.security.filter.oauth.JwtExceptionFilter; +import kr.co.morandi.backend.member_management.infrastructure.security.filter.oauth.RequestCachingFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfigurationSource; +import static org.springframework.http.HttpMethod.GET; + @EnableWebSecurity @Configuration @RequiredArgsConstructor @@ -25,6 +27,7 @@ public class SecurityConfig { private final JwtExceptionFilter jwtExceptionFilter; private final RequestCachingFilter requestCachingFilter; private final CorsConfigurationSource corsConfigurationSource; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ http @@ -34,9 +37,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .authorizeHttpRequests(authorize -> authorize .requestMatchers("/oauths/**","/swagger-ui/**", "/swagger-resources/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/daily-record/rankings/**").permitAll() + .requestMatchers(GET, "/daily-defense/**").permitAll() .anyRequest().authenticated()) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + ) .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .addFilterBefore(jwtAuthenticationFilter, BasicAuthenticationFilter.class) + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class) .addFilterBefore(requestCachingFilter, JwtExceptionFilter.class); diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java index 7a54b97c..7ca291b5 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/AuthenticationProvider.java @@ -9,26 +9,31 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; +import java.util.List; + @Component @RequiredArgsConstructor @Slf4j public class AuthenticationProvider { - private final OAuthUserDetailsService oAuthUserDetailsService; - private final SecretKeyProvider secretKeyProvider; + public void setAuthentication(String accessToken) { Authentication authentication = getAuthentication(accessToken); SecurityContextHolder.getContext().setAuthentication(authentication); } private Authentication getAuthentication(String accessToken) { Long memberId = getMemberIdFromToken(accessToken); - UserDetails userDetails = oAuthUserDetailsService.loadUserByUsername(memberId.toString()); - return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + //TODO : authorities를 이후에 저장해야함 + List authorities = null;//getAuthoritiesFromToken(accessToken); + + return new UsernamePasswordAuthenticationToken(memberId, null, authorities); } private Long getMemberIdFromToken(String token) { Jws claimsJws = Jwts.parserBuilder().setSigningKey(secretKeyProvider.getPublicKey()) diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java index 1d932ec1..b2fd8bf5 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/IgnoredURIManager.java @@ -11,7 +11,8 @@ public class IgnoredURIManager { "/oauths/", "/swagger-ui/", "/v3/api-docs/", - "/swagger-resources/" + "/swagger-resources/", + "/daily-record/rankings" }; private String PATTERN_STRING = String.join("|", IGNORED_URIS); public Pattern PATTERN = Pattern.compile(PATTERN_STRING); diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java index b28f5dc5..8bc4e1c2 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/config/security/utils/SecurityUtils.java @@ -1,11 +1,42 @@ package kr.co.morandi.backend.member_management.infrastructure.config.security.utils; +import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; public class SecurityUtils { public static Long getCurrentMemberId() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - return Long.valueOf(authentication.getName()); + + if (authentication == null || !authentication.isAuthenticated() || + authentication instanceof AnonymousAuthenticationToken) { + return null; + } + + Object principal = authentication.getPrincipal(); + + /* + * 일반적인 JWT 토큰을 사용할 때 발생. + * */ + if (principal instanceof Long) { + return (Long) principal; + } + /* + * @WithMockUser를 포함한 테스트나 기타 UserDetails 서비스를 사용할 때 발생한다. + * */ + if (principal instanceof UserDetails) { + /* + * principal이 UserDetails 타입인 경우, getUsername()에서 사용자 ID를 추출 + * 따라서 @WithMockUser를 사용할 때는 getUsername에 Member ID를 넣어줘야 한다. + */ + UserDetails userDetails = (UserDetails) principal; + try { + return Long.valueOf(userDetails.getUsername()); + } catch (NumberFormatException e) { + return null; + } + } + return null; } } diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java index c4ddb706..5e09e8a5 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java @@ -11,7 +11,7 @@ public enum OAuthErrorCode implements ErrorCode { MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST,"사용자를 찾을 수 없습니다"), - EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED,"인증 시간이 만료된 토큰입니다."), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED,"인증 시간이 만료된 토큰입니다. 다시 로그인하세요"), INVALID_TOKEN(HttpStatus.UNAUTHORIZED,"유효하지 않은 토큰입니다."), TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "액세스 토큰을 찾을 수 없습니다"), ACCESS_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "액세스 토큰을 찾을 수 없습니다."), diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/entrypoint/JwtAuthenticationEntryPoint.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/entrypoint/JwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..9f24f76c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/entrypoint/JwtAuthenticationEntryPoint.java @@ -0,0 +1,59 @@ +package kr.co.morandi.backend.member_management.infrastructure.security.filter.entrypoint; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import kr.co.morandi.backend.common.exception.response.ErrorResponse; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.JwtProvider; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final ObjectMapper objectMapper; + private final JwtProvider jwtProvider; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + + /* + * AccessToken이 존재하지 않는 경우 + * */ + if(jwtProvider.parseAccessToken(request) == null) { + response.setStatus(OAuthErrorCode.ACCESS_TOKEN_NOT_FOUND.getHttpStatus().value()); + response.getWriter().write(objectMapper.writeValueAsString(ErrorResponse.of(OAuthErrorCode.ACCESS_TOKEN_NOT_FOUND))); + response.getWriter().flush(); + + return; + } + /* + * RefreshToken이 존재하지 않는 경우 + * */ + if(jwtProvider.parseRefreshToken(request) == null) { + response.setStatus(OAuthErrorCode.REFRESH_TOKEN_NOT_FOUND.getHttpStatus().value()); + response.setContentType("application/json"); + response.getWriter().write(objectMapper.writeValueAsString(ErrorResponse.of(OAuthErrorCode.REFRESH_TOKEN_NOT_FOUND))); + response.getWriter().flush(); + + return; + } + + /* + * RefreshToken이 만료된 경우 + * */ + response.setStatus(OAuthErrorCode.EXPIRED_TOKEN.getHttpStatus().value()); + response.getWriter().write(objectMapper.writeValueAsString(ErrorResponse.of(OAuthErrorCode.EXPIRED_TOKEN))); + + + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/CachedBodyHttpServletWrapper.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/CachedBodyHttpServletWrapper.java similarity index 91% rename from src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/CachedBodyHttpServletWrapper.java rename to src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/CachedBodyHttpServletWrapper.java index a675a9be..51c6231c 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/CachedBodyHttpServletWrapper.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/CachedBodyHttpServletWrapper.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; +package kr.co.morandi.backend.member_management.infrastructure.security.filter.oauth; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; @@ -8,6 +8,7 @@ import org.springframework.util.StreamUtils; import java.io.*; +import java.nio.charset.StandardCharsets; public class CachedBodyHttpServletWrapper extends HttpServletRequestWrapper { private final byte[] cachedBody; @@ -26,7 +27,7 @@ public ServletInputStream getInputStream() throws IOException { @Override public BufferedReader getReader() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody); - return new BufferedReader(new InputStreamReader(byteArrayInputStream, "UTF-8")); + return new BufferedReader(new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8)); } public static class CachedBodyServletInputStream extends ServletInputStream { diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtAuthenticationFilter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/JwtAuthenticationFilter.java similarity index 56% rename from src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtAuthenticationFilter.java rename to src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/JwtAuthenticationFilter.java index 11a0e398..e0c2aebd 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtAuthenticationFilter.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/JwtAuthenticationFilter.java @@ -1,18 +1,17 @@ -package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; +package kr.co.morandi.backend.member_management.infrastructure.security.filter.oauth; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import kr.co.morandi.backend.common.exception.MorandiException; -import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.JwtValidator; -import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.JwtProvider; +import kr.co.morandi.backend.member_management.infrastructure.config.jwt.utils.JwtValidator; import kr.co.morandi.backend.member_management.infrastructure.config.security.utils.AuthenticationProvider; import kr.co.morandi.backend.member_management.infrastructure.config.security.utils.IgnoredURIManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; + import java.io.IOException; @Component @@ -43,32 +42,35 @@ protected void doFilterInternal(HttpServletRequest request, String accessToken = jwtProvider.parseAccessToken(request); String refreshToken = jwtProvider.parseRefreshToken(request); - /* - * accessToken이 유효한 경우, accessToken을 이용하여 인증을 수행하고 다음 필터로 넘어간다. - * */ - if (jwtValidator.validateToken(accessToken)) { // accessToken이 유효할 경우 - authenticationProvider.setAuthentication(accessToken); + if (accessToken != null && refreshToken != null) { + /* + * accessToken이 유효한 경우, accessToken을 이용하여 인증을 수행하고 다음 필터로 넘어간다. + * */ + if (jwtValidator.validateToken(accessToken)) { + authenticationProvider.setAuthentication(accessToken); - filterChain.doFilter(request, response); - return ; - } + filterChain.doFilter(request, response); + return; + } + /* + * accessToken의 유효 기간이 만료된 경우, refreshToken을 이용하여 accessToken을 재발급하고 다음 필터로 넘어간다. + * */ + else if (jwtValidator.validateToken(refreshToken)) { + // refreshToken이 유효할 경우 + accessToken = jwtProvider.reissueAccessToken(refreshToken); + response.setHeader("Authorization", "Bearer " + accessToken); - /* - * accessToken의 유효 기간이 만료된 경우, refreshToken을 이용하여 accessToken을 재발급하고 다음 필터로 넘어간다. - * */ - if (jwtValidator.validateToken(refreshToken)) { // refreshToken이 유효할 경우 - accessToken = jwtProvider.reissueAccessToken(refreshToken); - response.setHeader("Authorization", "Bearer " + accessToken); - - authenticationProvider.setAuthentication(accessToken); - filterChain.doFilter(request, response); - return ; + authenticationProvider.setAuthentication(accessToken); + filterChain.doFilter(request, response); + return; + } } /* - * refreshToken의 유효 기간도 만료된 경우, refreshToken이 만료되었다는 오류를 반환한다. + * accessToken이나 refreshToken이 없는 경우 다음 필터로 넘어가고 + * entryPoint에서 authentication되지 않은 요청에 대한 응답을 처리한다. * */ - throw new MorandiException(OAuthErrorCode.EXPIRED_TOKEN); + filterChain.doFilter(request, response); } } diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtExceptionFilter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/JwtExceptionFilter.java similarity index 85% rename from src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtExceptionFilter.java rename to src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/JwtExceptionFilter.java index cdda741a..3cc7de28 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/JwtExceptionFilter.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/JwtExceptionFilter.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; +package kr.co.morandi.backend.member_management.infrastructure.security.filter.oauth; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.FilterChain; @@ -31,16 +31,15 @@ public class JwtExceptionFilter extends OncePerRequestFilter { private final CookieUtils cookieUtils; - @Value("${oauth2.signup-url}") - private String signupPath; @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) throws IOException { + /* + * 다음 필터인 JwtAuthenticationFilter에서 발생한 예외를 처리하기 위해 필터를 실행한다. + * */ try { - /* - * 다음 필터인 JwtAuthenticationFilter에서 발생한 예외를 처리하기 위해 필터를 실행한다. - * */ filterChain.doFilter(request, response); } catch (MorandiException e) { /* @@ -49,7 +48,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (isAuthError(e)) { Cookie cookie = cookieUtils.removeCookie(REFRESH_TOKEN,null); response.addCookie(cookie); - response.sendRedirect(signupPath); } /* @@ -64,7 +62,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } } private boolean isAuthError(MorandiException e) { - return e.getErrorCode().getHttpStatus() == (HttpStatus.UNAUTHORIZED); + return e.getErrorCode().getHttpStatus() == (HttpStatus.UNAUTHORIZED) || e.getErrorCode().getHttpStatus() == HttpStatus.FORBIDDEN; } private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode) { response.setStatus(errorCode.getHttpStatus().value()); @@ -75,7 +73,8 @@ private void setErrorResponse(HttpServletResponse response, ErrorCode errorCode) try { response.getWriter().write(objectMapper.writeValueAsString(errorResponse)); - } catch (IOException e){ + response.getWriter().flush(); + } catch (IOException e) { log.error("IOException occurred while writing error response", e); } } diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/RequestCachingFilter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/RequestCachingFilter.java similarity index 89% rename from src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/RequestCachingFilter.java rename to src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/RequestCachingFilter.java index cb6a4063..8efd9bf6 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/filter/oauth/RequestCachingFilter.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/security/filter/oauth/RequestCachingFilter.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.member_management.infrastructure.filter.oauth; +package kr.co.morandi.backend.member_management.infrastructure.security.filter.oauth; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; diff --git a/src/main/java/kr/co/morandi/backend/problem_information/application/port/out/problemcontent/ProblemContentPort.java b/src/main/java/kr/co/morandi/backend/problem_information/application/port/out/problemcontent/ProblemContentPort.java new file mode 100644 index 00000000..498a1896 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/problem_information/application/port/out/problemcontent/ProblemContentPort.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.problem_information.application.port.out.problemcontent; + +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; + +import java.util.List; +import java.util.Map; + +public interface ProblemContentPort { + + Map getProblemContents(List baekjoonProblemIds); +} diff --git a/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/ProblemContent.java b/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/ProblemContent.java new file mode 100644 index 00000000..1dd2ea5d --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/ProblemContent.java @@ -0,0 +1,48 @@ +package kr.co.morandi.backend.problem_information.application.response.problemcontent; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +import java.util.List; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ProblemContent { + + private Long baekjoonProblemId; + private String title; + private String memoryLimit; + private String timeLimit; + private String description; + private String input; + private String output; + private List samples; + private String hint; + private List subtasks; + private String problemLimit; + private String additionalTimeLimit; + private String additionalJudgeInfo; + + // 오류 날 경우 error 필드만 반환됨 + private String error; + + + @Builder + private ProblemContent(Long baekjoonProblemId, String title, String memoryLimit, String timeLimit, String description, String input, String output, List samples, String hint, List subtasks, String problemLimit, String additionalTimeLimit, String additionalJudgeInfo, String error) { + this.baekjoonProblemId = baekjoonProblemId; + this.title = title; + this.memoryLimit = memoryLimit; + this.timeLimit = timeLimit; + this.description = description; + this.input = input; + this.output = output; + this.samples = samples; + this.hint = hint; + this.subtasks = subtasks; + this.problemLimit = problemLimit; + this.additionalTimeLimit = additionalTimeLimit; + this.additionalJudgeInfo = additionalJudgeInfo; + this.error = error; + } +} diff --git a/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/SampleData.java b/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/SampleData.java new file mode 100644 index 00000000..b532fecb --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/SampleData.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.problem_information.application.response.problemcontent; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SampleData { + + private String input; + private String output; + private String explanation; + + @Builder + private SampleData(String input, String output, String explanation) { + this.input = input; + this.output = output; + this.explanation = explanation; + } +} + diff --git a/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/Subtask.java b/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/Subtask.java new file mode 100644 index 00000000..899b9183 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/problem_information/application/response/problemcontent/Subtask.java @@ -0,0 +1,23 @@ +package kr.co.morandi.backend.problem_information.application.response.problemcontent; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.*; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Subtask { + + private String title; + private List conditions; + private String tableConditionsHtml; + + @Builder + private Subtask(String title, List conditions, String tableConditionsHtml) { + this.title = title; + this.conditions = conditions; + this.tableConditionsHtml = tableConditionsHtml; + } +} diff --git a/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapter.java b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapter.java new file mode 100644 index 00000000..8d2ebea0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapter.java @@ -0,0 +1,70 @@ +package kr.co.morandi.backend.problem_information.infrastructure.adapter.problemcontent; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.problem_information.application.port.out.problemcontent.ProblemContentPort; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.util.retry.Retry; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class ProblemContentAdapter implements ProblemContentPort { + + private final WebClient webClient; + private final ObjectMapper objectMapper; + + private static final String PROBLEM_CONTENTS_API_URL = "https://n1bcmtru2j.execute-api.ap-northeast-2.amazonaws.com/default/getBaekjoonProblemContents?baekjoonProblemIds=%s"; + + @Override + public Map getProblemContents(List baekjoonProblemIds) { + + if(baekjoonProblemIds.isEmpty()) { + return Map.of(); + } + + if(baekjoonProblemIds.size() > 10) { + throw new IllegalArgumentException("문제 번호는 10개 이하로 요청해주세요."); + } + + String baekjoonProblemIdsParam = baekjoonProblemIds.stream() + .map(String::valueOf) + .collect(Collectors.joining(",")); + + String responseBody = webClient.get() + .uri(String.format(PROBLEM_CONTENTS_API_URL, baekjoonProblemIdsParam)) + .retrieve() + .bodyToMono(String.class) + .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) + .maxBackoff(Duration.ofSeconds(5)) + .jitter(0.5)) + .block(); + + return parseResponse(responseBody); + } + + + private Map parseResponse(String responseBody) { + try { + objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + List problemContents = objectMapper.readValue(responseBody, new TypeReference<>() { + }); + + return problemContents.stream() + .filter(content -> content.getError() == null && content.getBaekjoonProblemId() != null) + .collect(Collectors.toMap(ProblemContent::getBaekjoonProblemId, content -> content)); + + } catch (Exception e) { + throw new RuntimeException("Error parsing problem contents", e); + } + } + +} diff --git a/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java new file mode 100644 index 00000000..815d75c2 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend; + +import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; +import kr.co.morandi.backend.defense_information.infrastructure.controller.DailyDefenseController; +import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; +import kr.co.morandi.backend.defense_record.infrastructure.controller.DailyRecordController; +import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.filter.OncePerRequestFilter; + +@WebMvcTest(controllers = { + DailyDefenseController.class, + DailyRecordController.class +}, + excludeAutoConfiguration = SecurityAutoConfiguration.class, + excludeFilters = { + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { + OncePerRequestFilter.class + }) +} +) +@ActiveProfiles("test") +public abstract class ControllerTestSupport { + + @Autowired + protected MockMvc mockMvc; + + @MockBean + protected DailyDefenseUseCase dailyDefenseUseCase; + + @MockBean + protected CookieUtils cookieUtils; + + @MockBean + protected DailyRecordRankUseCase dailyRecordRankUseCase; + +} diff --git a/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java b/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java new file mode 100644 index 00000000..cc5ed845 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("test") +@SpringBootTest +public abstract class IntegrationTestSupport { +} diff --git a/src/test/java/kr/co/morandi/backend/NewMorandiApplicationTests.java b/src/test/java/kr/co/morandi/backend/NewMorandiApplicationTests.java index 15f09e85..2ac08c14 100644 --- a/src/test/java/kr/co/morandi/backend/NewMorandiApplicationTests.java +++ b/src/test/java/kr/co/morandi/backend/NewMorandiApplicationTests.java @@ -1,10 +1,8 @@ package kr.co.morandi.backend; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -@SpringBootTest -class NewMorandiApplicationTests { +class NewMorandiApplicationTests extends IntegrationTestSupport{ @Test void contextLoads() { diff --git a/src/test/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapperTest.java b/src/test/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapperTest.java new file mode 100644 index 00000000..793009da --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_information/application/mapper/dailydefense/DailyDefenseInfoMapperTest.java @@ -0,0 +1,107 @@ +package kr.co.morandi.backend.defense_information.application.mapper.dailydefense; + +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + + +@ActiveProfiles("test") +class DailyDefenseInfoMapperTest { + + @DisplayName("시도한 적이 있는 DailyDefense Response DTO를 반환할 수 있다.") + @Test + void ofNonAttempted() { + // given + DailyDefense dailyDefense = createDailyDefense(); + + // when + DailyDefenseInfoResponse response = DailyDefenseInfoMapper.fromNonAttempted(dailyDefense); + + // then + assertThat(response) + .extracting("defenseName", "problemCount", "attemptCount") + .contains(dailyDefense.getContentName(), dailyDefense.getDailyDefenseProblems().size(), 0L); + + assertThat(response.getProblems()) + .extracting("problemNumber", "baekjoonProblemId", "difficulty", "solvedCount", "submitCount", "isSolved") + .containsExactlyInAnyOrder( + tuple(1L, 1L, B5, 0L, 0L, null), + tuple(2L, 2L, S5, 0L, 0L, null), + tuple(3L, 3L, G5, 0L, 0L, null) + ); + + + } + + @DisplayName("시도한 적이 있는 DailyDefense Response DTO를 반환할 수 있다.") + @Test + void ofAttempted() { + // given + DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map problems = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + + // when + DailyDefenseInfoResponse response = DailyDefenseInfoMapper.ofAttempted(dailyDefense, dailyRecord); + + // then + assertThat(response) + .extracting("defenseName", "problemCount", "attemptCount") + .contains(dailyDefense.getContentName(), dailyDefense.getDailyDefenseProblems().size(), 1L); + + assertThat(response.getProblems()) + .extracting("problemNumber", "baekjoonProblemId", "difficulty", "solvedCount", "submitCount", "isSolved") + .containsExactlyInAnyOrder( + tuple(1L, 1L, B5, 0L, 0L, false), + tuple(2L, 2L, S5, 0L, 0L, false), + tuple(3L, 3L, G5, 0L, 0L, false) + ); + } + + private Map getProblems(DailyDefense DailyDefense, Long problemNumber) { + return DailyDefense.getDailyDefenseProblems().stream() + .filter(p -> p.getProblemNumber().equals(problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + + private DailyDefense createDailyDefense() { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p -> problemNumber.getAndIncrement(), problem -> problem)); + LocalDate createdDate = LocalDate.of(2024, 3, 1); + return DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap); + } + + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + return List.of(problem1, problem2, problem3); + } + + private Member createMember(String name) { + return Member.create(name, name + "@gmail.com", GOOGLE, name, name); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java b/src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java new file mode 100644 index 00000000..8068edc0 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java @@ -0,0 +1,138 @@ +package kr.co.morandi.backend.defense_information.application.service; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; +import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; +import static org.junit.jupiter.api.Assertions.assertAll; + +@Transactional +class DailyDefenseUseCaseImplTest extends IntegrationTestSupport { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyDefenseUseCase dailyDefenseUseCase; + + @Autowired + private ProblemGenerationService problemGenerationService; + + @Autowired + private DailyRecordPort dailyRecordPort; + + @DisplayName("사용자가 없을 때 DailyDefense 정보를 조회하면 isSolved는 null로 반환한다.") + @Test + void getDailyDefenseInfo() { + // given + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); + + // when + final DailyDefenseInfoResponse response = dailyDefenseUseCase.getDailyDefenseInfo(null, requestTime); + + // then + assertAll( + () -> assertThat(response).isNotNull() + .extracting("defenseName", "problemCount", "attemptCount") + .contains("오늘의 문제 테스트", 3, 0L), + + () -> assertThat(response.getProblems()).hasSize(3) + .extracting("problemNumber", "baekjoonProblemId", "difficulty", "solvedCount", "submitCount", "isSolved") + .containsExactlyInAnyOrder( + tuple(1L, 1000L, B5, 0L, 0L, null), + tuple(2L, 2000L, S5, 0L, 0L, null), + tuple(3L, 3000L, G5, 0L, 0L, null) + ) + ); + } + + @DisplayName("사용자가 있을 때 응시 기록도 있다면 DailyDefense 정보를 조회하면 isSolved는 True/False를 포함하여 반환한다.") + @Test + void getDailyDefenseInfoWithMemberAndRecord() { + // given + + final DailyDefense dailyDefense = createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + Member member = createMember(); + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); + + final DailyRecord dailyRecord = createDailyRecord(dailyDefense, member, 2L, requestTime); + dailyRecord.solveProblem(2L, "example", requestTime.plusHours(1)); + dailyRecordPort.saveDailyRecord(dailyRecord); + + // when + final DailyDefenseInfoResponse response = dailyDefenseUseCase.getDailyDefenseInfo(member.getMemberId(), requestTime); + + // then + assertAll( + () -> assertThat(response).isNotNull() + .extracting("defenseName", "problemCount", "attemptCount") + .contains("오늘의 문제 테스트", 3, 1L), + + () -> assertThat(response.getProblems()).hasSize(3) + .extracting("problemNumber", "baekjoonProblemId", "difficulty", "solvedCount", "submitCount", "isSolved") + .containsExactlyInAnyOrder( + tuple(1L, 1000L, B5, 0L, 0L, false), + tuple(2L, 2000L, S5, 0L, 0L, true), + tuple(3L, 3000L, G5, 0L, 0L, false) + ) + ); + } + + // 시험에 응시하는 메소드 + private DailyRecord createDailyRecord(DailyDefense dailyDefense, Member member, Long problemNumber, LocalDateTime requestTIme) { + final Map tryingProblem = dailyDefense.getTryingProblem(problemNumber, problemGenerationService); + final DailyRecord dailyRecord = DailyRecord.tryDefense(requestTIme, dailyDefense, member, tryingProblem); + dailyRecord.tryMoreProblem(dailyDefense.getTryingProblem(3L, problemGenerationService)); + + return dailyRecordPort.saveDailyRecord(dailyRecord); + } + private DailyDefense createDailyDefense(LocalDate createdDate, String contentName) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return dailyDefenseRepository.save(DailyDefense.create(createdDate, contentName, problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.create(1000L, B5, 0L); + Problem problem2 = Problem.create(2000L, S5, 0L); + Problem problem3 = Problem.create(3000L, G5, 0L); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java index af6109f3..b060f6f6 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java @@ -1,18 +1,16 @@ package kr.co.morandi.backend.defense_information.domain.service.dailydefense; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; -import kr.co.morandi.backend.defense_information.domain.service.dailydefense.DailyDefenseGenerationService; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; import java.util.List; @@ -21,9 +19,7 @@ import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest -@ActiveProfiles("test") -class DailyDefenseGenerationServiceTest { +class DailyDefenseGenerationServiceTest extends IntegrationTestSupport { @Autowired private DailyDefenseGenerationService dailyDefenseGenerationService; diff --git a/src/test/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationServiceTest.java index 362a3c01..24448372 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/defense/ProblemGenerationServiceTest.java @@ -1,5 +1,6 @@ package kr.co.morandi.backend.defense_information.domain.service.defense; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; @@ -23,9 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -@SpringBootTest -@ActiveProfiles("test") -class ProblemGenerationServiceTest { +class ProblemGenerationServiceTest extends IntegrationTestSupport { @Autowired private ProblemGenerationService problemGenerationService; diff --git a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapterTest.java index 7cd85c8d..c455d76a 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/adapter/dailydefense/DailyDefenseProblemAdapterTest.java @@ -1,18 +1,16 @@ package kr.co.morandi.backend.defense_information.infrastructure.adapter.dailydefense; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; -import kr.co.morandi.backend.defense_information.infrastructure.adapter.dailydefense.DailyDefenseProblemAdapter; import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.time.LocalDate; import java.util.List; @@ -22,9 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -@SpringBootTest -@ActiveProfiles("test") -class DailyDefenseProblemAdapterTest { +class DailyDefenseProblemAdapterTest extends IntegrationTestSupport { @Autowired private ProblemRepository problemRepository; diff --git a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java new file mode 100644 index 00000000..ff01117a --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java @@ -0,0 +1,45 @@ +package kr.co.morandi.backend.defense_information.infrastructure.controller; + +import kr.co.morandi.backend.ControllerTestSupport; +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class DailyDefenseControllerTest extends ControllerTestSupport { + + + @DisplayName("DailyDefense 정보를 로그인하지 않은 상태에서 가져올 수 있다.") + @Test + void getDailyDefenseInfo() throws Exception { + // given + when(dailyDefenseUseCase.getDailyDefenseInfo(any(), any())) + .thenReturn(DailyDefenseInfoResponse.builder() + .problems(List.of()) + .defenseName("test") + .attemptCount(1L) + .problemCount(5) + .build()); + + // when + final ResultActions perform = mockMvc.perform(get("/daily-defense")); + + // then + perform + .andExpect(status().isOk()) + .andExpect(jsonPath("$.problems").isArray()) + .andExpect(jsonPath("$.defenseName").isString()) + .andExpect(jsonPath("$.attemptCount").isNumber()) + .andExpect(jsonPath("$.problemCount").isNumber()); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java index 05e19131..836a5485 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/algorithm/AlgorithmRepositoryTest.java @@ -1,21 +1,19 @@ package kr.co.morandi.backend.defense_information.infrastructure.persistence.algorithm; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.problem_information.domain.model.algorithm.Algorithm; import kr.co.morandi.backend.problem_information.infrastructure.persistence.algorithm.AlgorithmRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest -@ActiveProfiles("test") -class AlgorithmRepositoryTest { + +class AlgorithmRepositoryTest extends IntegrationTestSupport { @Autowired private AlgorithmRepository algorithmRepository; diff --git a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java index eaa69588..69b07601 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/customdefense/CustomDefenseRepositoryTest.java @@ -1,8 +1,7 @@ package kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense.CustomDefenseProblemRepository; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.customdefense.CustomDefenseRepository; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; @@ -11,23 +10,19 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; import java.util.List; -import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseTier.*; import static kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility.CLOSE; import static kr.co.morandi.backend.defense_information.domain.model.customdefense.Visibility.OPEN; +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseTier.*; import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; -@SpringBootTest -@ActiveProfiles("test") -class CustomDefenseRepositoryTest { +class CustomDefenseRepositoryTest extends IntegrationTestSupport { @Autowired private CustomDefenseRepository customDefenseRepository; diff --git a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepositoryTest.java index 86c953b4..4cdcc50d 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/dailydefense/DailyDefenseProblemRepositoryTest.java @@ -1,18 +1,15 @@ package kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense; -import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.time.LocalDate; import java.util.List; @@ -24,9 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -@SpringBootTest -@ActiveProfiles("test") -class DailyDefenseProblemRepositoryTest { +class DailyDefenseProblemRepositoryTest extends IntegrationTestSupport { @Autowired private DailyDefenseProblemRepository dailyDefenseProblemRepository; diff --git a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepositoryTest.java index 1188f548..bc9cd7b8 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/persistence/randomdefense/RandomDefenseRepositoryTest.java @@ -1,15 +1,13 @@ package kr.co.morandi.backend.defense_information.infrastructure.persistence.randomdefense; -import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.randomdefense.RandomDefenseRepository; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.util.List; @@ -17,9 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; -@SpringBootTest -@ActiveProfiles("test") -class RandomDefenseRepositoryTest { +class RandomDefenseRepositoryTest extends IntegrationTestSupport { @Autowired private RandomDefenseRepository randomDefenseRepository; diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapperTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapperTest.java new file mode 100644 index 00000000..ef04a710 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/mapper/tempcode/TempCodeMapperTest.java @@ -0,0 +1,67 @@ +package kr.co.morandi.backend.defense_management.application.mapper.tempcode; + +import kr.co.morandi.backend.defense_management.application.response.tempcode.TempCodeResponse; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.util.Set; + +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@ActiveProfiles("test") +class TempCodeMapperTest { + + @DisplayName("아무 것도 포함하지 않고 생성하면 초기화된 TempCodeResponses를 반환한다.") + @Test + void createTempCodeResponses() { + // given + Set tempCodes = Set.of(); + + // when + final Set responses = TempCodeMapper.createTempCodeResponses(tempCodes); + + // then + assertThat(responses).hasSize(3) + .extracting("language", "code") + .containsExactlyInAnyOrder( + tuple(JAVA, JAVA.getInitialCode()), + tuple(PYTHON, PYTHON.getInitialCode()), + tuple(CPP, CPP.getInitialCode()) + ); + + } + + @DisplayName("일부 언어를 포함하고 나머지는 초기화된 TempCodeResponses를 반환한다.") + @Test + void createTempCodeResponsesWithSomeTempCodes() { + //given + Set tempCodes = Set.of( + TempCode.builder() + .language(JAVA) + .code("java code") + .build(), + TempCode.builder() + .language(PYTHON) + .code("python code") + .build() + ); + + // when + final Set responses = TempCodeMapper.createTempCodeResponses(tempCodes); + + // then + assertThat(responses).hasSize(3) + .extracting("language", "code") + .containsExactlyInAnyOrder( + tuple(JAVA, "java code"), + tuple(PYTHON, "python code"), + tuple(CPP, CPP.getInitialCode()) + ); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPortTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPortTest.java index e5d21651..ce1d4802 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPortTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPortTest.java @@ -1,22 +1,20 @@ package kr.co.morandi.backend.defense_management.application.port.out.session; -import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; -import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; -import kr.co.morandi.backend.member_management.domain.model.member.Member; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.time.LocalDate; import java.time.LocalDateTime; @@ -29,9 +27,7 @@ import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest -@ActiveProfiles("test") -class DefenseSessionPortTest { +class DefenseSessionPortTest extends IntegrationTestSupport { @Autowired private DefenseSessionPort defenseSessionPort; diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java index d1f32cfc..a9569415 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java @@ -1,25 +1,34 @@ package kr.co.morandi.backend.defense_management.application.service.dailydefense; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; -import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; -import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseServiceResponse; -import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; -import kr.co.morandi.backend.member_management.domain.model.member.Member; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDetailRepository; +import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; +import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; +import kr.co.morandi.backend.defense_management.application.service.timer.DefenseTimerService; +import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.SessionDetailRepository; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDetailRepository; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.adapter.problemcontent.ProblemContentAdapter; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; -import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.event.ApplicationEvents; +import org.springframework.test.context.event.RecordApplicationEvents; +import org.springframework.transaction.support.TransactionTemplate; import java.time.LocalDate; import java.time.LocalDateTime; @@ -34,11 +43,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.*; -@SpringBootTest -@ActiveProfiles("test") -class DailyDefenseManagementServiceTest { +@RecordApplicationEvents +class DailyDefenseManagementServiceTest extends IntegrationTestSupport { @Autowired private DailyDefenseManagementService dailyDefenseManagementService; @@ -67,6 +78,38 @@ class DailyDefenseManagementServiceTest { @Autowired private SessionDetailRepository sessionDetailRepository; + @MockBean + private ProblemContentAdapter problemContentAdapter; + + @Autowired + private ApplicationEvents applicationEvents; + + @Autowired + private TransactionTemplate transactionTemplate; + + @MockBean + private DefenseTimerService defenseTimerService; + + @BeforeEach + void setUp() { + Map problemContentMap = Map.of( + 1L, ProblemContent.builder() + .baekjoonProblemId(1000L) + .title("test") + .build(), + 2L, ProblemContent.builder() + .baekjoonProblemId(2000L) + .title("test2") + .build(), + 3L, ProblemContent.builder() + .baekjoonProblemId(3000L) + .title("test3") + .build() + ); + Mockito.when(problemContentAdapter.getProblemContents(anyList())) + .thenReturn(problemContentMap); + } + @AfterEach void tearDown() { sessionDetailRepository.deleteAll(); @@ -79,6 +122,68 @@ void tearDown() { memberRepository.deleteAllInBatch(); } + @DisplayName("오늘의 문제가 시작되다가 롤백되면 타이머 이벤트가 실행되지 않는다.") + @Test + void eventPublishWhenStartDailyDefenseWhenRollback() { + // given + Member member = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + + // when + transactionTemplate.execute(status -> { + dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + status.setRollbackOnly(); // Force rollback + return null; + }); + + // then + assertThat(applicationEvents.stream(DefenseStartTimerEvent.class)) + .hasSize(1) + .anySatisfy(event -> { + assertAll( + () -> assertThat(event.getSessionId()).isNotNull(), + () -> assertThat(event.getStartDateTime()).isNotNull(), + () -> assertThat(event.getEndDateTime()).isNotNull() + ); + }); + + verify(defenseTimerService, never()).startDefenseTimer(any(), any(), any()); + } + + @DisplayName("오늘의 문제가 시작될 때 타이머 이벤트를 발행한다.") + @Test + void eventPublishWhenStartDailyDefense() { + // given + Member member = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + + // when + dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + + // then + assertThat(applicationEvents.stream(DefenseStartTimerEvent.class)) + .hasSize(1) + .anySatisfy(event -> { + assertAll( + () -> assertThat(event.getSessionId()).isNotNull(), + () -> assertThat(event.getStartDateTime()).isNotNull(), + () -> assertThat(event.getEndDateTime()).isNotNull() + ); + }); + verify(defenseTimerService, times(1)).startDefenseTimer(any(), any(), any()); + } @DisplayName("전날 시작했던 시험이 안 끝났더라도 오늘의 문제를 시도하면 해당하는 날짜의 문제를 제공한다.") @Test void retryDailyDefenseWhenDayPassed() { @@ -92,7 +197,7 @@ void retryDailyDefenseWhenDayPassed() { StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() .problemNumber(1L) .build(); - dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); StartDailyDefenseServiceRequest retryRequest = StartDailyDefenseServiceRequest.builder() .problemNumber(2L) @@ -101,7 +206,7 @@ void retryDailyDefenseWhenDayPassed() { LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 2, 12, 0, 0); // when - final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member, retryRequestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member.getMemberId(), retryRequestTime); // then @@ -131,7 +236,7 @@ void retryDailyDefenseWithOtherProblem() { StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() .problemNumber(1L) .build(); - dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); StartDailyDefenseServiceRequest retryRequest = StartDailyDefenseServiceRequest.builder() .problemNumber(2L) @@ -139,7 +244,7 @@ void retryDailyDefenseWithOtherProblem() { LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); // when - final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member, retryRequestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member.getMemberId(), retryRequestTime); // then assertAll( @@ -168,12 +273,12 @@ void retryDailyDefense() { StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() .problemNumber(2L) .build(); - dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); // when - final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(request, member, retryRequestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), retryRequestTime); // then assertAll( @@ -203,7 +308,7 @@ void startDailyDefense() { .build(); // when - final StartDailyDefenseServiceResponse response = dailyDefenseManagementService.startDailyDefense(request, member, requestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); // then diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java index cf4d8bb0..2fa35445 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java @@ -1,12 +1,13 @@ package kr.co.morandi.backend.defense_management.domain.model.session; +import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -26,6 +27,48 @@ @ActiveProfiles("test") class DefenseSessionTest { + @DisplayName("세션을 종료할 수 있다.") + @Test + void terminateSession() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + Map problems = getProblems(dailyDefense, 1L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + + // when + final boolean result = defenseSession.terminateSession(); + + // then + assertThat(result).isTrue(); + + } + + @DisplayName("세션이 종료상태일 때 종료하려하면 false를 반환한다.") + @Test + void terminateSessionWhenAlreadyTerminated() { + // given + Member member = createMember(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + Map problems = getProblems(dailyDefense, 1L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + defenseSession.terminateSession(); + + // when & then + assertThatThrownBy( + () -> defenseSession.terminateSession() + ) + .isInstanceOf(MorandiException.class) + .hasMessage("이미 종료된 세션입니다."); + + } @DisplayName("문제 번호를 가지고 있는지 확인한다.") @Test diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventServiceTest.java new file mode 100644 index 00000000..3347b6be --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventServiceTest.java @@ -0,0 +1,71 @@ +package kr.co.morandi.backend.defense_management.domain.service; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.defense_management.application.service.timer.DefenseTimerService; +import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.transaction.support.TransactionTemplate; + +import java.time.LocalDateTime; + +import static org.mockito.Mockito.*; + +class DefenseEventServiceTest extends IntegrationTestSupport { + + @Autowired + private ApplicationEventPublisher publisher; + + @MockBean + private DefenseTimerService defenseTimerService; + + @Autowired + private DefenseEventService defenseEventService; + + @Autowired + private TransactionTemplate transactionTemplate; + + + @DisplayName("DefenseStartTimerEvent가 발생하면 DefenseTimerService의 startDefenseTimer가 호출된다.") + @Test + void onDefenseStartTimerEvent() { + // given + LocalDateTime startDateTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + LocalDateTime endDateTime = LocalDateTime.of(2021, 10, 1, 0, 1, 0); + DefenseStartTimerEvent event = new DefenseStartTimerEvent(1L, startDateTime, endDateTime); + + // when + transactionTemplate.execute(status -> { + publisher.publishEvent(event); + return null; + }); + + // then + verify(defenseTimerService, times(1)) + .startDefenseTimer(1L, startDateTime, endDateTime); + } + + @DisplayName("DefenseStartTimerEvent 발행 후 rollback되면 DefenseTimerService의 startDefenseTimer가 호출되지 않는다.") + @Test + void onDefenseStartTimerEventRollback() { + // given + LocalDateTime startDateTime = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + LocalDateTime endDateTime = LocalDateTime.of(2021, 10, 1, 0, 1, 0); + DefenseStartTimerEvent event = new DefenseStartTimerEvent(1L, startDateTime, endDateTime); + + // when + transactionTemplate.execute(status -> { + publisher.publishEvent(event); + status.setRollbackOnly(); + return null; + }); + + // then + verify(defenseTimerService, never()) + .startDefenseTimer(1L, startDateTime, endDateTime); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java new file mode 100644 index 00000000..7f616e14 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java @@ -0,0 +1,209 @@ +package kr.co.morandi.backend.defense_management.domain.service; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_management.domain.model.session.ExamStatus; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.domain.model.record.RecordStatus; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@Transactional +class SessionServiceTest extends IntegrationTestSupport { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DefenseSessionRepository defenseSessionRepository; + + @Autowired + private SessionService sessionService; + + + @DisplayName("DailyDefense를 시작했을 때 세션과 Record를 종료할 수 있다.") + @Test + void terminateDefense() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + Member member = createMember(); + DailyRecord dailyRecord = tryDailyDefense(today, member); + DefenseSession session = DefenseSession.builder() + .recordId(dailyRecord.getRecordId()) + .problemNumbers(Set.of(2L)) + .startDateTime(today) + .endDateTime(today.plusMinutes(1)) + .build(); + + final DefenseSession defenseSession = defenseSessionRepository.save(session); + + // when + sessionService.terminateDefense(defenseSession.getDefenseSessionId()); + + // then + defenseSessionRepository.findById(defenseSession.getDefenseSessionId()) + .ifPresent(s -> assertEquals(s.getExamStatus(), ExamStatus.COMPLETED)); + + } + + @DisplayName("Session이 종료된 상태에서 세션을 종료하려고 하면 예외가 발생한다.") + @Test + void terminateDefenseWhenSessionTerminated() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + Member member = createMember(); + DailyRecord dailyRecord = tryDailyDefense(today, member); + DefenseSession session = DefenseSession.builder() + .recordId(dailyRecord.getRecordId()) + .problemNumbers(Set.of(2L)) + .startDateTime(today) + .endDateTime(today.plusMinutes(1)) + .build(); + + final DefenseSession defenseSession = defenseSessionRepository.save(session); + defenseSession.terminateSession(); + + // when & then + assertThatThrownBy(() -> sessionService.terminateDefense(defenseSession.getDefenseSessionId())) + .isInstanceOf(MorandiException.class) + .hasMessageContaining("이미 종료된 세션입니다."); + } + + @DisplayName("없는 Session ID로 세션을 종료하려고 하면 예외가 발생한다.") + @Test + void terminateDefenseWhenSessionNotFound() { + // given + Long notFoundSessionId = 1L; + + + // when & then + assertThatThrownBy(() -> sessionService.terminateDefense(notFoundSessionId)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining("세션을 찾을 수 없습니다."); + } + + @DisplayName("Session이 종료된 상태에서 세션을 종료하려고 하면 예외가 발생한다.") + @Test + void terminateDefenseWhenRecordTerminated() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + Member member = createMember(); + DailyRecord dailyRecord = tryDailyDefense(today, member); + DefenseSession session = DefenseSession.builder() + .recordId(dailyRecord.getRecordId()) + .problemNumbers(Set.of(2L)) + .startDateTime(today) + .endDateTime(today.plusMinutes(1)) + .build(); + dailyRecord.terminteDefense(); + + final DefenseSession defenseSession = defenseSessionRepository.save(session); + + + + // when & then + assertThatThrownBy(() -> sessionService.terminateDefense(defenseSession.getDefenseSessionId())) + .isInstanceOf(MorandiException.class) + .hasMessageContaining("이미 종료된 시험 기록입니다."); + + } + + private DailyRecord tryDailyDefense(LocalDateTime today, Member member) { + final DailyDefense dailyDefense = createDailyDefense(today.toLocalDate()); + final Map problem = getProblem(dailyDefense, 2L); + + DailyRecord dailyRecord = DailyRecord.builder() + .testDate(today) + .defense(dailyDefense) + .status(RecordStatus.IN_PROGRESS) + .member(member) + .build(); + + final DailyRecord savedDailyRecord = dailyRecordRepository.save(dailyRecord); + + final DailyDetail dailyDetail = DailyDetail.builder() + .member(member) + .problemNumber(1L) + .problem(problem.get(1L)) + .record(savedDailyRecord) + .defense(dailyDefense) + .build(); + + savedDailyRecord.getDetails().add(dailyDetail); + + return dailyRecordRepository.save(savedDailyRecord); + } + + private Map getProblem(DailyDefense dailyDefense, Long problemNumber) { + return dailyDefense.getDailyDefenseProblems().stream() + .filter(problem -> Objects.equals(problem.getProblemNumber(), problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + + private DailyDefense createDailyDefense(LocalDate createdDate) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + + return dailyDefenseRepository.save(DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.builder() + .baekjoonProblemId(1L) + .problemTier(B5) + .build(); + + Problem problem2 = Problem.builder() + .baekjoonProblemId(2L) + .problemTier(S5) + .build(); + + Problem problem3 = Problem.builder() + .baekjoonProblemId(3L) + .problemTier(G5) + .build(); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.builder() + .email("test") + .build()); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java index 1b2db030..e0682886 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapterTest.java @@ -1,18 +1,17 @@ package kr.co.morandi.backend.defense_management.infrastructure.adapter.session; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; -import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.SessionDetailRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; import java.util.Optional; @@ -21,9 +20,7 @@ import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest -@ActiveProfiles("test") -class DefenseSessionAdapterTest { +class DefenseSessionAdapterTest extends IntegrationTestSupport { @Autowired private DefenseSessionPort defenseSessionPort; diff --git a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementControllerTest.java b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementControllerTest.java new file mode 100644 index 00000000..3b92d9b8 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementControllerTest.java @@ -0,0 +1,10 @@ +package kr.co.morandi.backend.defense_management.infrastructure.controller; + +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.junit.jupiter.api.Assertions.*; + +class DefenseMangementControllerTest { + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/application/DailyRecordRankUseCaseTest.java b/src/test/java/kr/co/morandi/backend/defense_record/application/DailyRecordRankUseCaseTest.java new file mode 100644 index 00000000..6ab76909 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_record/application/DailyRecordRankUseCaseTest.java @@ -0,0 +1,129 @@ +package kr.co.morandi.backend.defense_record.application; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_record.application.dto.DailyDefenseRankPageResponse; +import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@Transactional +class DailyRecordRankUseCaseTest extends IntegrationTestSupport { + + @Autowired + private DailyRecordRankUseCase dailyRecordRankUseCase; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private MemberRepository memberRepository; + + @DisplayName("특정 시점 DailyRecord의 순위를 조회할 수 있다.") + @Test + void getDailyRecordsRankByDate() { + // given + LocalDate today = LocalDate.of(2021, 10, 1); + final DailyDefense dailyDefense = createDailyDefense(today); + + final Member member1 = createMember("userA", "userA"); + final Member member2 = createMember("userB", "userB"); + final Member member3 = createMember("userC", "userC"); + final Member member4= createMember("userD", "userD"); + final Member member5 = createMember("userE", "userE"); + + final DailyRecord dailyRecord1 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member1, getProblem(dailyDefense, 1L)); + final DailyRecord dailyRecord2 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member2, getProblem(dailyDefense, 2L)); + final DailyRecord dailyRecord3 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member3, getProblem(dailyDefense, 3L)); + final DailyRecord dailyRecord4 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member4, getProblem(dailyDefense, 3L)); + final DailyRecord dailyRecord5 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member5, getProblem(dailyDefense, 3L)); + + /* + * member1: 한 문제 해결 일찍 + * member2 : 두 문제 해결 + * member3: 한 문제 해결 1보다 늦게 + * + * -> 등수 = 2 -> 1 -> 3 + * */ + dailyRecord1.solveProblem(1L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 15)); + + dailyRecord2.solveProblem(2L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 30)); + dailyRecord2.tryMoreProblem(getProblem(dailyDefense, 3L)); + dailyRecord2.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 45)); + + dailyRecord3.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 1, 0)); + + dailyRecordRepository.saveAll(List.of(dailyRecord1, dailyRecord2, dailyRecord3, dailyRecord4, dailyRecord5)); + + + // when + LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 2, 0); + final DailyDefenseRankPageResponse dailyRecordRank = dailyRecordRankUseCase.getDailyRecordRank(requestTime, 0, 5); + + // then + assertThat(dailyRecordRank.getDailyRecords()).hasSize(5) + .extracting("rank", "nickname", "solvedCount", "totalSolvedTime") + .containsExactly( + tuple(1L, "userB", 2L, "01:15:00"), + tuple(2L, "userA", 1L, "00:15:00"), + tuple(3L, "userC", 1L, "01:00:00"), + //TODO 동점자 처리 로직 반영 X + tuple(4L, "userD", 0L, "00:00:00"), + tuple(5L, "userE", 0L, "00:00:00") + + ); + } + + private Map getProblem(DailyDefense dailyDefense, Long problemNumber) { + return dailyDefense.getDailyDefenseProblems().stream() + .filter(problem -> Objects.equals(problem.getProblemNumber(), problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + + private DailyDefense createDailyDefense(LocalDate createdDate) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return dailyDefenseRepository.save(DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.create(1L, B5, 0L); + Problem problem2 = Problem.create(2L, S5, 0L); + Problem problem3 = Problem.create(3L, G5, 0L); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember(String nickname, String email) { + return memberRepository.save(Member.create(nickname, email + "@gmail.com", GOOGLE, "test", "test")); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelperTest.java b/src/test/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelperTest.java new file mode 100644 index 00000000..a76c8f74 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_record/application/util/TimeFormatHelperTest.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.defense_record.application.util; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class TimeFormatHelperTest { + + @DisplayName("시간을 문자열로 변환한다") + @Test + void solvedTimeToString() { + // given + // 1시간 15분 37초를 초로 변환 + Long time = 3600L + (15L * 60L) + 37L; + + // when + String result = TimeFormatHelper.solvedTimeToString(time); + + // then + assertThat(result).isEqualTo("01:15:37"); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java index 1b970192..35a270db 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java @@ -1,10 +1,10 @@ package kr.co.morandi.backend.defense_record.domain.model.dailydefense_record; +import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; @@ -13,6 +13,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; @@ -20,20 +21,149 @@ import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.groups.Tuple.tuple; import static org.junit.jupiter.api.Assertions.assertNotNull; @ActiveProfiles("test") class DailyRecordTest { + @DisplayName("시험 기록(Record)를 종료하면 종료 상태로 변경된다.") + @Test + void terminateDefense() { + // given + DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); + + + // when + final boolean result = dailyRecord.terminteDefense(); + + // then + assertThat(result).isTrue(); + + } + + @DisplayName("시험 기록(Record)가 종료된 상태에서 다시 종료하려고 하면 false를 반환한다.") + @Test + void terminateDefenseWhenAlreadyTerminated() { + // given + DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); + dailyRecord.terminteDefense(); + + // when & then + assertThatThrownBy(() -> dailyRecord.terminteDefense()) + .isInstanceOf(MorandiException.class); + } + @DisplayName("오늘의 문제를 정답처리 하면 푼 total 문제 수가 증가하고, 푼 시간이 기록된다.") + @Test + void solveProblem() { + // given + DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); + + // when + dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 15, 0)); + + + // then + assertThat(dailyRecord) + .extracting("totalSolvedTime", "solvedCount") + .contains( + 15 * 60L, 1L + ); + assertThat(dailyRecord.getDetails()).hasSize(1) + .extracting("isSolved", "solvedTime") + .contains( + tuple(true, 15 * 60L) + ); + } + + @DisplayName("이미 정답처리된 문제를 정답 solved하려하면 바뀌지 않는다.") + @Test + void solveProblemWhenAlreadySolved() { + // given + DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); + dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 15, 0)); + + // when + dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 20, 0)); + + + + // then + assertThat(dailyRecord) + .extracting("totalSolvedTime", "solvedCount") + .contains( + 15 * 60L, 1L + ); + assertThat(dailyRecord.getDetails()).hasSize(1) + .extracting("isSolved", "solvedTime") + .contains( + tuple(true, 15 * 60L) + ); + } + @DisplayName("풀어낸 문제들에 대한 문제번호 목록을 반환할 수 있다.") + @Test + void getSolvedProblemNumbers() { + // given + DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); + dailyRecord.tryMoreProblem(getProblems(dailyDefense, 3L)); + dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 15, 0)); + + // when + final Set solvedProblemNumbers = dailyRecord.getSolvedProblemNumbers(); + + + // then + assertThat(solvedProblemNumbers).hasSize(1) + .contains(2L); + + } + + @DisplayName("시험에 응시하면 오늘의 문제 attemptCount가 1 증가한다.") + @Test + void increaseAttempCountWhenTryDefense() { + // given + DailyDefense dailyDefense = createDailyDefense(); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member member = createMember("user"); + Map triedProblem = getProblems(dailyDefense, 2L); + + // when + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); + + // then + assertThat(dailyDefense.getAttemptCount()).isEqualTo(1L); + + } + @DisplayName("오늘의 문제 기록에서 세부 문제의 정답 여부를 확인할 수 있다.") @Test void isSolvedProblem() { // given - DailyDefense DailyDefense = createDailyDefense(); + DailyDefense dailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - Map triedProblem = getProblems(DailyDefense, 2L); - DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, triedProblem); + Map triedProblem = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); // when final boolean solvedProblem = dailyRecord.isSolvedProblem(2L); @@ -46,11 +176,11 @@ void isSolvedProblem() { @Test void tryExistDetailThenReturnExistDetail() { // given - DailyDefense DailyDefense = createDailyDefense(); + DailyDefense dailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - Map triedProblem = getProblems(DailyDefense, 2L); - DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, triedProblem); + Map triedProblem = getProblems(dailyDefense, 2L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); // when dailyRecord.tryMoreProblem(triedProblem); @@ -65,13 +195,13 @@ void tryExistDetailThenReturnExistDetail() { @Test void solvedCountIsZero() { // given - DailyDefense DailyDefense = createDailyDefense(); + DailyDefense dailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - Map problems = getProblems(DailyDefense, 2L); + Map problems = getProblems(dailyDefense, 2L); // when - DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); // then assertThat(dailyDefenseRecord.getSolvedCount()).isZero(); @@ -99,15 +229,15 @@ void recordCreateExceptionWhenOverOneDay() { void recordCreatedWithinOneDay() { // given LocalDate createdDate = LocalDate.of(2024, 3, 1); - DailyDefense DailyDefense = createDailyDefense(createdDate); + DailyDefense dailyDefense = createDailyDefense(createdDate); Member member = createMember("user"); - Map problems = getProblems(DailyDefense, 2L); + Map problems = getProblems(dailyDefense, 2L); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 23, 59, 59); // when - DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); // then assertNotNull(dailyDefenseRecord); @@ -116,13 +246,13 @@ void recordCreatedWithinOneDay() { @Test void isSolvedIsFalse() { // given - DailyDefense DailyDefense = createDailyDefense(); + DailyDefense dailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - Map problems = getProblems(DailyDefense, 2L); + Map problems = getProblems(dailyDefense, 2L); // when - DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); // then assertThat(dailyDefenseRecord.getDetails()) @@ -133,13 +263,13 @@ void isSolvedIsFalse() { @Test void submitCountIsZero() { // given - DailyDefense DailyDefense = createDailyDefense(); + DailyDefense dailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - Map problems = getProblems(DailyDefense, 2L); + Map problems = getProblems(dailyDefense, 2L); // when - DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); // then assertThat(dailyDefenseRecord.getDetails()) @@ -150,13 +280,13 @@ void submitCountIsZero() { @Test void solvedCodeIsNull() { // given - DailyDefense DailyDefense = createDailyDefense(); + DailyDefense dailyDefense = createDailyDefense(); LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); Member member = createMember("user"); - Map problems = getProblems(DailyDefense,2L); + Map problems = getProblems(dailyDefense,2L); // when - DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, DailyDefense, member, problems); + DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); // then assertThat(dailyDefenseRecord.getDetails()) diff --git a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense_record/DailyRecordAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense_record/DailyRecordAdapterTest.java index 84711ca6..80a5ec0b 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense_record/DailyRecordAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/dailydefense_record/DailyRecordAdapterTest.java @@ -1,21 +1,20 @@ package kr.co.morandi.backend.defense_record.infrastructure.adapter.dailydefense_record; -import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; -import kr.co.morandi.backend.member_management.domain.model.member.Member; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; -import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.time.LocalDate; import java.time.LocalDateTime; @@ -30,9 +29,7 @@ import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest -@ActiveProfiles("test") -class DailyRecordAdapterTest { +class DailyRecordAdapterTest extends IntegrationTestSupport { @Autowired private DailyRecordPort dailyRecordPort; diff --git a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java new file mode 100644 index 00000000..33b0b957 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java @@ -0,0 +1,132 @@ +package kr.co.morandi.backend.defense_record.infrastructure.adapter.record; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class RecordAdapterTest extends IntegrationTestSupport { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private RecordAdapter recordAdapter; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @DisplayName("RecordId로 Record를 찾아올 수 있다.") + @Test + void findRecordById() { + // given + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + + final Member member = createMember(); + final DailyRecord dailyRecord = tryDailyDefense(today, member); + + // when + final Optional> record = recordAdapter.findRecordById(dailyRecord.getRecordId()); + + // then + assertThat(record).isPresent() + .get() + .extracting("recordId", "defense.contentName") + .contains(dailyRecord.getRecordId(), "오늘의 문제 테스트"); + + } + + private DailyRecord tryDailyDefense(LocalDateTime today, Member member) { + final DailyDefense dailyDefense = createDailyDefense(today.toLocalDate()); + final Map problem = getProblem(dailyDefense, 2L); + + DailyRecord dailyRecord = DailyRecord.builder() + .testDate(today) + .defense(dailyDefense) + .member(member) + .build(); + + final DailyRecord savedDailyRecord = dailyRecordRepository.save(dailyRecord); + + final DailyDetail dailyDetail = DailyDetail.builder() + .member(member) + .problemNumber(1L) + .problem(problem.get(1L)) + .record(savedDailyRecord) + .defense(dailyDefense) + .build(); + + savedDailyRecord.getDetails().add(dailyDetail); + + return dailyRecordRepository.save(savedDailyRecord); + } + + private Map getProblem(DailyDefense dailyDefense, Long problemNumber) { + return dailyDefense.getDailyDefenseProblems().stream() + .filter(problem -> Objects.equals(problem.getProblemNumber(), problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + + private DailyDefense createDailyDefense(LocalDate createdDate) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + + return dailyDefenseRepository.save(DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.builder() + .baekjoonProblemId(1L) + .problemTier(B5) + .build(); + + Problem problem2 = Problem.builder() + .baekjoonProblemId(2L) + .problemTier(S5) + .build(); + + Problem problem3 = Problem.builder() + .baekjoonProblemId(3L) + .problemTier(G5) + .build(); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.builder() + .email("test") + .build()); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordControllerTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordControllerTest.java new file mode 100644 index 00000000..017492bc --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/controller/DailyRecordControllerTest.java @@ -0,0 +1,51 @@ +package kr.co.morandi.backend.defense_record.infrastructure.controller; + +import kr.co.morandi.backend.ControllerTestSupport; +import kr.co.morandi.backend.defense_record.application.dto.DailyDefenseRankPageResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class DailyRecordControllerTest extends ControllerTestSupport { + + @DisplayName("[GET] DailyDefense 순위를 조회한다.") + @Test + void getDailyRecordRank() throws Exception { + // given + int page = 0; + int size = 5; + when(dailyRecordRankUseCase.getDailyRecordRank(any(), anyInt(), anyInt())) + .thenReturn(DailyDefenseRankPageResponse.builder() + .totalPage(1) + .currentPage(0) + .dailyRecords(List.of()) + .build()); + + + // when + ResultActions perform = mockMvc.perform( + get("/daily-record/rankings") + .param("page", String.valueOf(page)) + .param("size", String.valueOf(size) + )); + + + // then + perform + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalPage").isNumber()) + .andExpect(jsonPath("$.currentPage").isNumber()) + .andExpect(jsonPath("$.dailyRecords").isArray()); + } + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java index b1794f6b..d60a3986 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java @@ -1,21 +1,21 @@ package kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; -import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; -import kr.co.morandi.backend.member_management.domain.model.member.Member; -import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.LocalDateTime; @@ -31,16 +31,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -@SpringBootTest -@ActiveProfiles("test") -class DailyRecordRepositoryTest { +@Transactional +class DailyRecordRepositoryTest extends IntegrationTestSupport { @Autowired private DailyRecordRepository dailyRecordRepository; - @Autowired - private DailyDefenseProblemRepository dailyDefenseProblemRepository; - @Autowired private DailyDefenseRepository dailyDefenseRepository; @@ -50,15 +46,52 @@ class DailyRecordRepositoryTest { @Autowired private MemberRepository memberRepository; + @DisplayName("특정 시점 DailyRecord의 순위를 조회할 수 있다.") + @Test + void getDailyRecordsRankByDate() { + // given + LocalDate today = LocalDate.of(2021, 10, 1); + final DailyDefense dailyDefense = createDailyDefense(today); + + final Member member1 = createMember("userA", "userA"); + final Member member2 = createMember("userB", "userB"); + final Member member3 = createMember("userC", "userC"); + + final DailyRecord dailyRecord1 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member1, getProblem(dailyDefense, 1L)); + final DailyRecord dailyRecord2 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member2, getProblem(dailyDefense, 2L)); + final DailyRecord dailyRecord3 = DailyRecord.tryDefense(LocalDateTime.of(2021, 10, 1, 0, 0), dailyDefense, member3, getProblem(dailyDefense, 3L)); + + /* + * member1: 한 문제 해결 일찍 + * member2 : 두 문제 해결 + * member3: 한 문제 해결 1보다 늦게 + * + * -> 등수 = 2 -> 1 -> 3 + * */ + dailyRecord1.solveProblem(1L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 15)); + + dailyRecord2.solveProblem(2L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 30)); + dailyRecord2.tryMoreProblem(getProblem(dailyDefense, 3L)); + dailyRecord2.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 45)); + + dailyRecord3.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 1, 0)); + + dailyRecordRepository.saveAll(List.of(dailyRecord1, dailyRecord2, dailyRecord3)); + + + // when + Pageable pageable = PageRequest.of(0, 5); + Page dailyRecords = dailyRecordRepository.getDailyRecordsRankByDate(today, pageable); + + // then + assertThat(dailyRecords).hasSize(3) + .extracting(DailyRecord::getMember, DailyRecord::getSolvedCount, DailyRecord::getTotalSolvedTime) + .containsExactly(// 푼 시간은 초단위 + tuple(member2, 2L, 75L * 60), + tuple(member1, 1L, 15L * 60), + tuple(member3, 1L, 60L * 60) + ); - @AfterEach - void tearDown() { - dailyRecordRepository.deleteAll(); - dailyDefenseProblemRepository.deleteAllInBatch(); - dailyDefenseRepository.deleteAllInBatch(); - dailyRecordRepository.deleteAllInBatch(); - problemRepository.deleteAll(); - memberRepository.deleteAll(); } @DisplayName("원하는 recordId에 해당하는 DailyRecord가 존재할 때 찾아올 수 있다.") @@ -153,5 +186,8 @@ private List createProblems() { private Member createMember() { return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); } + private Member createMember(String nickname, String email) { + return memberRepository.save(Member.create(nickname, email + "@gmail.com", GOOGLE, "test", "test")); + } } \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java b/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java new file mode 100644 index 00000000..e35b275d --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.docs; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; + +@ExtendWith(RestDocumentationExtension.class) +public abstract class RestDocsSupport { + + protected MockMvc mockMvc; + protected ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + @BeforeEach + void setUp(RestDocumentationContextProvider provider) { + this.mockMvc = MockMvcBuilders.standaloneSetup(initController()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper)) + .apply(documentationConfiguration(provider)) + .build(); + } + + protected abstract Object initController(); +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java new file mode 100644 index 00000000..b657fcc9 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java @@ -0,0 +1,96 @@ +package kr.co.morandi.backend.docs.dailydefense; + +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; +import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseProblemInfoResponse; +import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; +import kr.co.morandi.backend.defense_information.infrastructure.controller.DailyDefenseController; +import kr.co.morandi.backend.docs.RestDocsSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +import java.util.List; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.S5; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class DailyDefenseControllerDocsTest extends RestDocsSupport { + + private final DailyDefenseUseCase dailyDefenseUseCase = mock(DailyDefenseUseCase.class); + @Override + protected Object initController() { + return new DailyDefenseController(dailyDefenseUseCase); + } + + @DisplayName("DailyDefense 정보를 가져오는 API") + @Test + void getDailyDefenseInfo() throws Exception { + + final DailyDefenseProblemInfoResponse problem = DailyDefenseProblemInfoResponse.builder() + .problemNumber(1L) + .problemId(1L) + .baekjoonProblemId(1000L) + .difficulty(S5) + .solvedCount(1L) + .submitCount(1L) + .isSolved(true) + .build(); + + when(dailyDefenseUseCase.getDailyDefenseInfo(any(), any())) + .thenReturn(DailyDefenseInfoResponse.builder() + .problems(List.of(problem)) + .defenseName("test") + .attemptCount(1L) + .problemCount(5) + .build()); + + final ResultActions perform = mockMvc.perform(get("/daily-defense")); + + perform + .andExpect(status().isOk()) + .andExpect(jsonPath("$.problems").isArray()) + .andExpect(jsonPath("$.defenseName").isString()) + .andExpect(jsonPath("$.attemptCount").isNumber()) + .andExpect(jsonPath("$.problemCount").isNumber()) + .andDo(document("daily-defense-info", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("defenseName").type(JsonFieldType.STRING) + .description("디펜스 이름"), + fieldWithPath("problemCount").type(JsonFieldType.NUMBER) + .description("총 문제 수"), + fieldWithPath("attemptCount").type(JsonFieldType.NUMBER) + .description("디펜스 시도 횟수"), + fieldWithPath("problems").type(JsonFieldType.ARRAY) + .description("디펜스 문제 목록"), + fieldWithPath("problems[].problemNumber").type(JsonFieldType.NUMBER) + .description("시도하는 문제 번호"), + fieldWithPath("problems[].problemId").type(JsonFieldType.NUMBER) + .description("시도하는 문제의 PK"), + fieldWithPath("problems[].baekjoonProblemId").type(JsonFieldType.NUMBER) + .description("백준 문제 번호"), + fieldWithPath("problems[].difficulty").type(JsonFieldType.STRING) + .description("문제 난이도 ex) SILVER"), + fieldWithPath("problems[].solvedCount").type(JsonFieldType.NUMBER) + .description("정답자 수"), + fieldWithPath("problems[].submitCount").type(JsonFieldType.NUMBER) + .description("제출한 사람 수"), + fieldWithPath("problems[].isSolved").type(JsonFieldType.BOOLEAN) + .optional() + .description("해당 사용자가 정답을 맞췄는지 여부, 이 필드가 없으면 아직 시도하지 않은 문제") + ) + )); + + } +} diff --git a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java new file mode 100644 index 00000000..40b3392a --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java @@ -0,0 +1,140 @@ +package kr.co.morandi.backend.docs.dailydefense; + +import kr.co.morandi.backend.defense_record.application.dto.DailyDefenseRankPageResponse; +import kr.co.morandi.backend.defense_record.application.dto.DailyDetailRankResponse; +import kr.co.morandi.backend.defense_record.application.dto.DailyRecordRankResponse; +import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; +import kr.co.morandi.backend.defense_record.application.util.TimeFormatHelper; +import kr.co.morandi.backend.defense_record.infrastructure.controller.DailyRecordController; +import kr.co.morandi.backend.docs.RestDocsSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class DailyRecordControllerDocsTest extends RestDocsSupport { + + private final DailyRecordRankUseCase dailyRecordRankUseCase = mock(DailyRecordRankUseCase.class); + + @Override + protected Object initController() { + return new DailyRecordController(dailyRecordRankUseCase); + } + + @DisplayName("오늘의 문제 랭킹 반환 API") + @Test + void getDailyRecordRank() throws Exception { + // 5개 문제에 대한 세부 정보 생성 + Map allDetails = new HashMap<>(); + for (long i = 1; i <= 5; i++) { // 5개의 문제 + allDetails.put(i, DailyDetailRankResponse.builder() + .problemNumber(i) + .isSolved(i % 2 == 0) // 홀수 문제는 해결하지 못함, 짝수 문제는 해결함 + .solvedTime(TimeFormatHelper.solvedTimeToString(i * 60000)) // 문제별로 1, 2, 3, 4, 5분 소요 + .build()); + } + + // 각 유저가 5개의 문제에 대한 세부 정보를 갖도록 설정 + List records = new ArrayList<>(); + for (long i = 1; i <= 5; i++) { // 5명의 유저 + List details = allDetails.values().stream() + .collect(Collectors.toList()); + + records.add(DailyRecordRankResponse.builder() + .nickname("test" + i) + .rank(i) + .solvedCount(details.stream().filter(DailyDetailRankResponse::getIsSolved).count()) // 해결한 문제 수 + .updatedAt(LocalDateTime.now()) + .totalSolvedTime(TimeFormatHelper.solvedTimeToString(details.stream() + .mapToLong(detail -> TimeFormatHelper.stringToSolvedTime(detail.getSolvedTime())) + .sum())) + .rankDetails(details) + .build()); + + } + + DailyDefenseRankPageResponse response = DailyDefenseRankPageResponse.builder() + .dailyRecords(records) + .totalPage(1) + .currentPage(0) + .build(); + + when(dailyRecordRankUseCase.getDailyRecordRank(any(), anyInt(), anyInt())) + .thenReturn(response); + + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("page", "0"); + params.add("size", "5"); + + + final ResultActions perform = mockMvc.perform(get("/daily-record/rankings") + .params(params)); + + perform + .andExpect(status().isOk()) + .andDo(document("daily-defense-ranking", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + fieldWithPath("dailyRecords") + .type(JsonFieldType.ARRAY) + .description("오늘의 문제 순위 기록 목록"), + fieldWithPath("dailyRecords[].nickname") + .type(JsonFieldType.STRING) + .description("사용자 닉네임"), + fieldWithPath("dailyRecords[].rank") + .type(JsonFieldType.NUMBER) + .description("사용자의 순위"), + fieldWithPath("dailyRecords[].solvedCount") + .type(JsonFieldType.NUMBER) + .description("해결한 문제 수"), + fieldWithPath("dailyRecords[].updatedAt") + .type(JsonFieldType.STRING) + .description("최근 업데이트 시간"), + fieldWithPath("dailyRecords[].totalSolvedTime") + .type(JsonFieldType.STRING) + .description("총 해결 시간"), + fieldWithPath("dailyRecords[].rankDetails") + .type(JsonFieldType.ARRAY) + .description("문제별 세부 순위 정보"), + fieldWithPath("dailyRecords[].rankDetails[].problemNumber") + .type(JsonFieldType.NUMBER) + .description("문제 번호"), + fieldWithPath("dailyRecords[].rankDetails[].isSolved") + .type(JsonFieldType.BOOLEAN) + .description("해결 여부"), + fieldWithPath("dailyRecords[].rankDetails[].solvedTime") + .type(JsonFieldType.STRING) + .description("해결 시간"), + fieldWithPath("totalPage") + .type(JsonFieldType.NUMBER) + .description("전체 페이지 수"), + fieldWithPath("currentPage") + .type(JsonFieldType.NUMBER) + .description("현재 페이지 번호") + ))); + + + } +} diff --git a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java new file mode 100644 index 00000000..01909971 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java @@ -0,0 +1,209 @@ +package kr.co.morandi.backend.docs.dailydefense; + +import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; +import kr.co.morandi.backend.defense_management.application.response.session.DefenseProblemResponse; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; +import kr.co.morandi.backend.defense_management.application.response.tempcode.TempCodeResponse; +import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.defense_management.infrastructure.controller.DefenseMangementController; +import kr.co.morandi.backend.defense_management.infrastructure.request.dailydefense.StartDailyDefenseRequest; +import kr.co.morandi.backend.docs.RestDocsSupport; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.SampleData; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.Subtask; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class DefenseManagementControllerDocsTest extends RestDocsSupport { + + private final DailyDefenseManagementService dailyDefenseManagementService = mock(DailyDefenseManagementService.class); + + @Override + protected Object initController() { + return new DefenseMangementController(dailyDefenseManagementService); + } + + @DisplayName("DailyDefense를 시작하는 API") + @Test + void getDailyDefenseInfo() throws Exception { + Subtask subtask = Subtask.builder() + .title("Basic Cases") + .conditions(List.of("Input range 1-100", "No negative numbers")) + .tableConditionsHtml("
Condition
Input range 1-100
No negative numbers
") + .build(); + + SampleData sampleData = SampleData.builder() + .input("1 2") + .output("3") + .explanation("The output 3 is the sum of 1 and 2.") + .build(); + + ProblemContent problemContent = ProblemContent.builder() + .baekjoonProblemId(1001L) + .title("A+B Problem") + .memoryLimit("128MB") + .timeLimit("1s") + .description("Calculate A + B.") + .input("Two integers A and B.") + .output("Output of A + B.") + .samples(List.of(sampleData)) + .hint("Use simple addition.") + .subtasks(List.of(subtask)) + .problemLimit("No specific limits.") + .additionalTimeLimit("None") + .additionalJudgeInfo("Standard problem.") + .error(null) + .build(); + + TempCodeResponse java = TempCodeResponse.builder() + .language(Language.JAVA) + .code("public class Main { public static void main(String[] args) { System.out.println(1 + 1); } }") + .build(); + + TempCodeResponse cpp = TempCodeResponse.builder() + .language(Language.CPP) + .code("#include \nint main() { std::cout << 1 + 1 << std::endl; return 0; }") + .build(); + + TempCodeResponse python = TempCodeResponse.builder() + .language(Language.PYTHON) + .code("print(1 + 1)") + .build(); + + DefenseProblemResponse defenseProblemResponse = DefenseProblemResponse.builder() + .problemId(100L) + .problemNumber(1L) + .baekjoonProblemId(1001L) + .content(problemContent) + .isCorrect(true) + .lastAccessLanguage(Language.JAVA) + .tempCodes(Set.of(java, cpp, python)) + .build(); + final StartDailyDefenseResponse startDailyDefenseResponse = StartDailyDefenseResponse.builder() + .defenseSessionId(101L) + .contentName("Daily Challenge") + .defenseType(DefenseType.DAILY) + .lastAccessTime(LocalDateTime.of(2021, 4, 27, 0, 0, 0)) + .defenseProblems(List.of(defenseProblemResponse)) + .build(); + + when(dailyDefenseManagementService.startDailyDefense(any(), any(), any())) + .thenReturn(startDailyDefenseResponse); + + final StartDailyDefenseRequest request = StartDailyDefenseRequest.builder() + .problemNumber(1L) + .build(); + + final ResultActions perform = mockMvc.perform(post("/daily-defense") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + perform + .andExpect(status().isOk()) + .andDo(document("daily-defense-start", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + responseFields( + // StartDailyDefenseResponse + fieldWithPath("defenseSessionId").type(JsonFieldType.NUMBER) + .description("오늘의 문제 세션의 고유 ID"), + fieldWithPath("contentName").type(JsonFieldType.STRING) + .description("오늘의 문제 이름"), + fieldWithPath("defenseType").type(JsonFieldType.STRING) + .description("디펜스 유형"), + fieldWithPath("lastAccessTime").type(JsonFieldType.STRING) + .description("마지막으로 콘텐츠에 접근한 시간"), + fieldWithPath("defenseProblems").type(JsonFieldType.ARRAY) + .description("오늘의 문제의 문제 목록[]"), + + // DefenseProblemResponse - Array Element + fieldWithPath("defenseProblems[].problemId").type(JsonFieldType.NUMBER) + .description("문제의 고유 ID"), + fieldWithPath("defenseProblems[].problemNumber").type(JsonFieldType.NUMBER) + .description("문제 번호"), + fieldWithPath("defenseProblems[].baekjoonProblemId").type(JsonFieldType.NUMBER) + .description("백준 문제 ID"), + fieldWithPath("defenseProblems[].content").type(JsonFieldType.OBJECT) + .description("문제의 내용 상세"), + fieldWithPath("defenseProblems[].isCorrect").type(JsonFieldType.BOOLEAN) + .description("문제가 올바르게 해결되었는지 여부"), + fieldWithPath("defenseProblems[].lastAccessLanguage").type(JsonFieldType.STRING) + .description("사용된 마지막 프로그래밍 언어"), + fieldWithPath("defenseProblems[].tempCodes").type(JsonFieldType.ARRAY) + .description("문제에 대해 작성된 임시 코드[]"), + + // ProblemContent within DefenseProblemResponse + fieldWithPath("defenseProblems[].content.baekjoonProblemId").type(JsonFieldType.NUMBER) + .description("백준 문제 ID"), + fieldWithPath("defenseProblems[].content.title").type(JsonFieldType.STRING) + .description("문제의 제목"), + fieldWithPath("defenseProblems[].content.memoryLimit").type(JsonFieldType.STRING) + .description("문제의 메모리 제한"), + fieldWithPath("defenseProblems[].content.timeLimit").type(JsonFieldType.STRING) + .description("문제의 시간 제한"), + fieldWithPath("defenseProblems[].content.description").type(JsonFieldType.STRING) + .description("문제의 설명"), + fieldWithPath("defenseProblems[].content.input").type(JsonFieldType.STRING) + .description("문제의 입력 형식"), + fieldWithPath("defenseProblems[].content.output").type(JsonFieldType.STRING) + .description("문제의 출력 형식"), + fieldWithPath("defenseProblems[].content.samples").type(JsonFieldType.ARRAY) + .description("문제의 샘플 입력/출력 값 배열"), + fieldWithPath("defenseProblems[].content.samples[].input").type(JsonFieldType.STRING) + .description("문제의 샘플 입력값"), + fieldWithPath("defenseProblems[].content.samples[].output").type(JsonFieldType.STRING) + .description("문제의 샘플 출력값"), + fieldWithPath("defenseProblems[].content.samples[].explanation").type(JsonFieldType.STRING) + .optional() + .description("입출력 예제에 대한 설명"), + fieldWithPath("defenseProblems[].content.hint").type(JsonFieldType.STRING) + .optional() + .description("문제를 해결하기 위한 힌트"), + fieldWithPath("defenseProblems[].content.subtasks").type(JsonFieldType.ARRAY) + .description("서브테스크 목록"), + fieldWithPath("defenseProblems[].content.subtasks[].title").type(JsonFieldType.STRING) + .description("부분 작업의 제목"), + fieldWithPath("defenseProblems[].content.subtasks[].conditions").type(JsonFieldType.ARRAY) + .description("부분 작업의 조건 목록 (일반적으로 conditions 와 tableConditionsHtml 중 1개가 주어짐)"), + fieldWithPath("defenseProblems[].content.subtasks[].tableConditionsHtml").type(JsonFieldType.STRING) + .description("HTML 형식의 조건 표"), + fieldWithPath("defenseProblems[].content.problemLimit").type(JsonFieldType.STRING) + .optional() + .description("문제 제한"), + fieldWithPath("defenseProblems[].content.additionalTimeLimit").type(JsonFieldType.STRING) + .optional() + .description("추가 시간 제한"), + fieldWithPath("defenseProblems[].content.additionalJudgeInfo").type(JsonFieldType.STRING) + .optional() + .description("추가 채점 정보"), + fieldWithPath("defenseProblems[].content.error").type(JsonFieldType.STRING) + .optional() + .description("문제 발생 시 반환되는 오류 필드만 반환됨"), + + // TempCodeResponse within DefenseProblemResponse + fieldWithPath("defenseProblems[].tempCodes[].language").type(JsonFieldType.STRING) + .description("임시 저장된 코드의 프로그래밍 언어"), + fieldWithPath("defenseProblems[].tempCodes[].code").type(JsonFieldType.STRING) + .description("임시 저장된 코드 스니펫") + ) + )); + + } +} diff --git a/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java index 1a637980..5f821f53 100644 --- a/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java @@ -1,20 +1,18 @@ package kr.co.morandi.backend.member_management.infrastructure.persistence.member; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.test.context.ActiveProfiles; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -@SpringBootTest -@ActiveProfiles("test") -class MemberRepositoryTest { +class MemberRepositoryTest extends IntegrationTestSupport { @Autowired private MemberRepository memberRepository; diff --git a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problem/ProblemAdapterTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problem/ProblemAdapterTest.java index 1154e496..dfa221a5 100644 --- a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problem/ProblemAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problem/ProblemAdapterTest.java @@ -1,9 +1,9 @@ package kr.co.morandi.backend.problem_information.infrastructure.adapter.problem; -import org.springframework.boot.test.context.SpringBootTest; +import kr.co.morandi.backend.IntegrationTestSupport; -@SpringBootTest -class ProblemAdapterTest { + +class ProblemAdapterTest extends IntegrationTestSupport { } \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapterTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapterTest.java new file mode 100644 index 00000000..fd31f7b4 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/adapter/problemcontent/ProblemContentAdapterTest.java @@ -0,0 +1,133 @@ +package kr.co.morandi.backend.problem_information.infrastructure.adapter.problemcontent; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.groups.Tuple.tuple; + +@ActiveProfiles("test") +class ProblemContentAdapterTest { + + private ProblemContentAdapter problemContentAdapter; + + private ExchangeFunction exchangeFunction; + + @BeforeEach + void setUp() { + ObjectMapper objectMapper = new ObjectMapper(); + + exchangeFunction = Mockito.mock(ExchangeFunction.class); + + WebClient webClient = WebClient.builder() + .exchangeFunction(exchangeFunction) + .build(); + + problemContentAdapter = new ProblemContentAdapter(webClient, objectMapper); + } + + + + @DisplayName("문제 번호 리스트를 받아서 해당 문제 번호의 문제 정보를 반환한다.") + @Test + void getProblemContents() { + // given + List list = List.of(1000L, 1001L); + + Mockito.when(exchangeFunction.exchange(Mockito.any())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "application/json") + .body(""" + [ + { + "baekjoonProblemId": 1000, + "title": "A+B" + }, + { + "baekjoonProblemId": 1001, + "title": "A-B" + } + ]""") + .build())); + + + // when + final Map result = problemContentAdapter.getProblemContents(list); + + // then + assertThat(result.values()).hasSize(2) + .extracting("baekjoonProblemId", "title") + .containsExactlyInAnyOrder( + tuple(1000L, "A+B"), + tuple(1001L, "A-B") + ); + + + } + + @DisplayName("존재하지 않는 문제 번호를 포함하여 요청하면 해당 문제 번호를 제외하고 반환한다.") + @Test + void getProblemContentsContainsInvalidBaekjoonProblemId() { + // given + List list = List.of(1000L, 1001L, 999L); + + Mockito.when(exchangeFunction.exchange(Mockito.any())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "application/json") + .body(""" + [ + { + "baekjoonProblemId": 1000, + "title": "A+B" + }, + { + "baekjoonProblemId": 1001, + "title": "A-B" + }, + { + "error" : "problem/999.json not exist" + } + ]""") + .build())); + + + // when + final Map result = problemContentAdapter.getProblemContents(list); + + // then + assertThat(result.values()).hasSize(2) + .extracting("baekjoonProblemId", "title") + .containsExactlyInAnyOrder( + tuple(1000L, "A+B"), + tuple(1001L, "A-B") + ); + + } + + @DisplayName("10개 이상의 문제 번호를 요청하면 예외가 발생한다.") + @Test + void getProblemContentsContainsMoreThan10BaekjoonProblemId() { + // given + List list = List.of(1000L, 1001L, 1002L, 1003L, 1004L, 1005L, 1006L, 1007L, 1008L, 1009L, 1010L); + + // when & then + assertThatThrownBy(() -> problemContentAdapter.getProblemContents(list)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("문제 번호는 10개 이하로 요청해주세요."); + + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/config/AlgorithmInitializerTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/config/AlgorithmInitializerTest.java index dff7a15b..85c0c152 100644 --- a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/config/AlgorithmInitializerTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/config/AlgorithmInitializerTest.java @@ -1,5 +1,6 @@ package kr.co.morandi.backend.problem_information.infrastructure.config; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.problem_information.domain.model.algorithm.Algorithm; import kr.co.morandi.backend.problem_information.infrastructure.initializer.AlgorithmInitializer; import kr.co.morandi.backend.problem_information.infrastructure.persistence.algorithm.AlgorithmRepository; @@ -7,8 +8,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import java.io.IOException; import java.util.List; @@ -16,9 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -@SpringBootTest -@ActiveProfiles("test") -class AlgorithmInitializerTest { +class AlgorithmInitializerTest extends IntegrationTestSupport { @Autowired private AlgorithmInitializer algorithmInitializer; diff --git a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java index a8fd239d..17d30b3e 100644 --- a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java @@ -1,15 +1,13 @@ package kr.co.morandi.backend.problem_information.infrastructure.persistence.problem; +import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; -import org.springframework.test.context.ActiveProfiles; import java.util.List; @@ -18,9 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; -@SpringBootTest -@ActiveProfiles("test") -class ProblemRepositoryTest { +class ProblemRepositoryTest extends IntegrationTestSupport { @Autowired private ProblemRepository problemRepository; diff --git a/src/test/resources/org.springframework.restdocs.templates/request-fields.snippet b/src/test/resources/org.springframework.restdocs.templates/request-fields.snippet new file mode 100644 index 00000000..95aa88f3 --- /dev/null +++ b/src/test/resources/org.springframework.restdocs.templates/request-fields.snippet @@ -0,0 +1,14 @@ +==== Request Fields +|=== +|Path|Type|Optional|Description + +{{#fields}} + +|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} +|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/fields}} + +|=== \ No newline at end of file diff --git a/src/test/resources/org.springframework.restdocs.templates/response-fields.snippet b/src/test/resources/org.springframework.restdocs.templates/response-fields.snippet new file mode 100644 index 00000000..795882b6 --- /dev/null +++ b/src/test/resources/org.springframework.restdocs.templates/response-fields.snippet @@ -0,0 +1,14 @@ +==== Response Fields +|=== +|Path|Type|Optional|Description + +{{#fields}} + +|{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} +|{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} +|{{#tableCellContent}}{{#optional}}O{{/optional}}{{/tableCellContent}} +|{{#tableCellContent}}{{description}}{{/tableCellContent}} + +{{/fields}} + +|=== \ No newline at end of file From d5f58e44b6a77fc0bf603cd986482b2b1a896acf Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Mon, 13 May 2024 13:55:55 +0900 Subject: [PATCH 12/14] =?UTF-8?q?=EC=8B=9C=ED=97=98=20=EC=8B=9C=EC=9E=91?= =?UTF-8?q?=EC=8B=9C=20SSE=20=EC=97=B0=EA=B2=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :art: Usecase는 패키지 추가하여 별도 관리하도록 수정 * :sparkles: 시험 시작시 SseEmitter 생성 기능 및 컨트롤러 생성 * :bug: 종료된 시험은 연결할 수 없게 변경 * :bug: 시험 종료 중 예외가 발생하는 경우 재처리 로직 추가 * :pencil2: 파사드 서비스 usecase로 이름 변경 * :white_check_mark: 테스트 추가 및 RestDocs 추가 --- .../api/defensesession/defensesession.adoc | 9 ++ src/docs/asciidoc/index.adoc | 3 +- .../DailyDefenseUseCaseImpl.java | 2 +- .../defensemessaging/DefenseMessagePort.java | 10 ++ .../message/CreateDefenseCableService.java | 25 +++ .../message/DefenseMessageService.java | 34 ++++ .../service/timer/DefenseTimerService.java | 69 +++++++- .../DailyDefenseManagementUsecase.java} | 10 +- .../domain/error/SessionErrorCode.java | 6 +- .../event/CreateDefenseMessageEvent.java | 11 ++ .../domain/model/session/DefenseSession.java | 11 +- .../DefenseMessageSseAdapter.java | 146 +++++++++++++++++ .../DefenseMangementController.java | 6 +- .../SessionConnectionController.java | 26 +++ .../DailyRecordRankUseCaseImpl.java | 2 +- .../backend/ControllerTestSupport.java | 11 +- .../message/DefenseMessageServiceTest.java | 150 ++++++++++++++++++ .../DailyDefenseManagementUsecaseTest.java} | 91 +++++++++-- .../model/session/DefenseSessionTest.java | 49 +++++- .../SessionConnectionControllerTest.java | 34 ++++ .../DailyRecordRankUseCaseTest.java | 2 +- .../morandi/backend/docs/RestDocsSupport.java | 54 +++---- .../DailyRecordControllerDocsTest.java | 1 + .../DefenseManagementControllerDocsTest.java | 4 +- .../SessionConnectionControllerDocsTest.java | 44 +++++ 25 files changed, 751 insertions(+), 59 deletions(-) create mode 100644 src/docs/asciidoc/api/defensesession/defensesession.adoc rename src/main/java/kr/co/morandi/backend/defense_information/application/{service => usecase}/DailyDefenseUseCaseImpl.java (97%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/service/message/CreateDefenseCableService.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageService.java rename src/main/java/kr/co/morandi/backend/defense_management/application/{service/session/DailyDefenseManagementService.java => usecase/session/DailyDefenseManagementUsecase.java} (94%) create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/domain/event/CreateDefenseMessageEvent.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionController.java rename src/main/java/kr/co/morandi/backend/defense_record/{application/service => usecase}/DailyRecordRankUseCaseImpl.java (97%) create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java rename src/test/java/kr/co/morandi/backend/defense_management/application/{service/dailydefense/DailyDefenseManagementServiceTest.java => usecase/dailydefense/DailyDefenseManagementUsecaseTest.java} (79%) create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionControllerTest.java rename src/test/java/kr/co/morandi/backend/defense_record/application/{ => usecase}/DailyRecordRankUseCaseTest.java (98%) create mode 100644 src/test/java/kr/co/morandi/backend/docs/dailydefense/SessionConnectionControllerDocsTest.java diff --git a/src/docs/asciidoc/api/defensesession/defensesession.adoc b/src/docs/asciidoc/api/defensesession/defensesession.adoc new file mode 100644 index 00000000..7575a2c6 --- /dev/null +++ b/src/docs/asciidoc/api/defensesession/defensesession.adoc @@ -0,0 +1,9 @@ +== 디펜스 세션 연결 + +=== Request +include::{snippets}/connect-session/http-request.adoc[] + +=== Response +include::{snippets}/connect-session/response-body.adoc[] +include::{snippets}/connect-session/http-response.adoc[] + diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index b23195f2..7f9a8885 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -9,8 +9,9 @@ endif::[] :toclevels: 1 :sectlinks: -include::api/dailydefense/dailydefense.adoc[] +include::api/defensesession/defensesession.adoc[] +include::api/dailydefense/dailydefense.adoc[] [[Daily-Defense-List]] \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImpl.java b/src/main/java/kr/co/morandi/backend/defense_information/application/usecase/DailyDefenseUseCaseImpl.java similarity index 97% rename from src/main/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImpl.java rename to src/main/java/kr/co/morandi/backend/defense_information/application/usecase/DailyDefenseUseCaseImpl.java index 5225abe4..cc80aae5 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImpl.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/application/usecase/DailyDefenseUseCaseImpl.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.defense_information.application.service; +package kr.co.morandi.backend.defense_information.application.usecase; import kr.co.morandi.backend.defense_information.application.dto.response.DailyDefenseInfoResponse; import kr.co.morandi.backend.defense_information.application.mapper.dailydefense.DailyDefenseInfoMapper; diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java new file mode 100644 index 00000000..4f01f835 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java @@ -0,0 +1,10 @@ +package kr.co.morandi.backend.defense_management.application.port.out.defensemessaging; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +public interface DefenseMessagePort { + + void createConnection(Long defenseSessionId); + SseEmitter getConnection(Long defenseSessionId); + boolean sendMessage(Long defenseSessionId, String message); +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/message/CreateDefenseCableService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/message/CreateDefenseCableService.java new file mode 100644 index 00000000..1bc437b4 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/message/CreateDefenseCableService.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.defense_management.application.service.message; + +import kr.co.morandi.backend.defense_management.application.port.out.defensemessaging.DefenseMessagePort; +import kr.co.morandi.backend.defense_management.domain.event.CreateDefenseMessageEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Service +@RequiredArgsConstructor +public class CreateDefenseCableService { + + private final DefenseMessagePort defenseMessagePort; + + /* + * Defense가 시작되면 DefenseMessagePort를 통해 SSE 연결을 생성한다. + * */ + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) + public void createConnection(CreateDefenseMessageEvent event) { + Long defenseSessionId = event.getDefenseSessionId(); + + defenseMessagePort.createConnection(defenseSessionId); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageService.java new file mode 100644 index 00000000..7d0e2fe9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageService.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.defense_management.application.service.message; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.application.port.out.defensemessaging.DefenseMessagePort; +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@Service +@RequiredArgsConstructor +public class DefenseMessageService { + + private final DefenseMessagePort defenseMessagePort; + private final DefenseSessionPort defenseSessionPort; + + public SseEmitter getConnection(Long defenseSessionId, Long memberId) { + DefenseSession defenseSession = defenseSessionPort.findDefenseSessionById(defenseSessionId) + .orElseThrow(() -> new MorandiException(SessionErrorCode.SESSION_NOT_FOUND)); + + /* + * 세션의 소유자인지 확인 + * 세션의 소유자가 아닐 경우 예외 발생 + * */ + defenseSession.validateSessionOwner(memberId); + if(defenseSession.isTerminated()) { + throw new MorandiException(SessionErrorCode.SESSION_TERMINATED); + } + return defenseMessagePort.getConnection(defenseSessionId); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java index 6a7bdb4b..b720b118 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/timer/DefenseTimerService.java @@ -1,7 +1,11 @@ package kr.co.morandi.backend.defense_management.application.service.timer; + import kr.co.morandi.backend.common.exception.MorandiException; + import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; import kr.co.morandi.backend.defense_management.domain.service.SessionService; + import kr.co.morandi.backend.defense_record.domain.error.RecordErrorCode; import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.Duration; @@ -11,6 +15,7 @@ import java.util.concurrent.TimeUnit; @Service + @Slf4j @RequiredArgsConstructor public class DefenseTimerService { @@ -21,9 +26,69 @@ public void startDefenseTimer(Long defenseSessionId, LocalDateTime startDateTime long delay = Duration.between(startDateTime, endDateTime).toMillis(); - scheduler.schedule(() -> - sessionService.terminateDefense(defenseSessionId), delay, TimeUnit.MILLISECONDS); + scheduler.schedule(() -> { + try { + sessionService.terminateDefense(defenseSessionId); + } + catch (MorandiException e) { + //SessionErrorCode.SESSION_ALREADY_ENDED인 경우는 이미 종료된 세션이므로 무시합니다. + if (e.getErrorCode().equals(SessionErrorCode.SESSION_ALREADY_ENDED)) { + return; + } + // RecordErrorCode.RECORD_ALREADY_TERMINATED인 경우는 이미 종료된 시험기록이므로 무시합니다. + if (e.getErrorCode().equals(RecordErrorCode.RECORD_ALREADY_TERMINATED)) { + return; + } + /* + * 예외 발생 시 다시 큐에 넣어 5초 후에 동작하도록 만들었고 + * 최대 3번까지 재시도하도록 만들었습니다. + * */ + retryTermination(defenseSessionId, 5000, 3); + } + catch (Exception e) { + /* + * 예외 발생 시 다시 큐에 넣어 5초 후에 동작하도록 만들었고 + * 최대 3번까지 재시도하도록 만들었습니다. + * */ + retryTermination(defenseSessionId, 5000, 3); + } + }, delay, TimeUnit.MILLISECONDS); } + private void retryTermination(Long defenseSessionId, long retryDelay, int retryCount) { + + /* + * 남은 재시도 횟수가 0이하일 경우 종료 + * */ + if(retryCount <= 0) { + log.error("재시도 횟수가 초과되어 시험 종료에 실패했습니다. defenseSessionId: {}", defenseSessionId); + return; + } + + + scheduler.schedule(() -> { + try { + sessionService.terminateDefense(defenseSessionId); + } + catch (MorandiException e) { + //SessionErrorCode.SESSION_ALREADY_ENDED인 경우는 이미 종료된 세션이므로 무시합니다. + if (e.getErrorCode().equals(SessionErrorCode.SESSION_ALREADY_ENDED)) { + return; + } + // RecordErrorCode.RECORD_ALREADY_TERMINATED인 경우는 이미 종료된 시험기록이므로 무시합니다. + if (e.getErrorCode().equals(RecordErrorCode.RECORD_ALREADY_TERMINATED)) { + return; + } + retryTermination(defenseSessionId, retryDelay, retryCount - 1); + } + catch (Exception e) { + /* + * 남은 재시도 횟수를 1 감소시키면서 재시도합니다. + * */ + retryTermination(defenseSessionId, retryDelay, retryCount - 1); + } + }, retryDelay, TimeUnit.MILLISECONDS); + } + } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/usecase/session/DailyDefenseManagementUsecase.java similarity index 94% rename from src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java rename to src/main/java/kr/co/morandi/backend/defense_management/application/usecase/session/DailyDefenseManagementUsecase.java index 15455ed3..6f31cbf4 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/service/session/DailyDefenseManagementService.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/usecase/session/DailyDefenseManagementUsecase.java @@ -1,4 +1,4 @@ - package kr.co.morandi.backend.defense_management.application.service.session; + package kr.co.morandi.backend.defense_management.application.usecase.session; import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; @@ -7,6 +7,7 @@ import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; + import kr.co.morandi.backend.defense_management.domain.event.CreateDefenseMessageEvent; import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; @@ -30,7 +31,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor - public class DailyDefenseManagementService { + public class DailyDefenseManagementUsecase { private final DailyDefensePort dailyDefensePort; private final DailyRecordPort dailyRecordPort; @@ -103,6 +104,11 @@ private DefenseSession createNewSession(Member member, LocalDateTime now, DailyD * */ publisher.publishEvent(new DefenseStartTimerEvent(defenseSession.getDefenseSessionId(), defenseSession.getStartDateTime(), defenseSession.getEndDateTime())); + /* + * DefenseMessage 연결 이벤트 발행 (현재는 SSE로 구현되어 있습니다.) + * */ + publisher.publishEvent(new CreateDefenseMessageEvent(defenseSession.getDefenseSessionId())); + return defenseSession; } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java index cdb6b248..989d5c04 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/error/SessionErrorCode.java @@ -10,8 +10,10 @@ public enum SessionErrorCode implements ErrorCode { SESSION_NOT_FOUND(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다."), SESSION_ALREADY_STARTED(HttpStatus.BAD_REQUEST, "이미 시작된 세션입니다."), - SESSION_ALREADY_ENDED(HttpStatus.BAD_REQUEST, "이미 종료된 세션입니다."),; - + SESSION_ALREADY_ENDED(HttpStatus.BAD_REQUEST, "이미 종료된 세션입니다."), + INVALID_SESSION_OWNER(HttpStatus.FORBIDDEN, "사용자의 시험 세션이 아닙니다."), + SESSION_CONNECTION_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "디펜스 세션 연결에 실패하였습니다."), + SESSION_TERMINATED(HttpStatus.BAD_REQUEST, "이미 종료된 디펜스 세션입니다."); @Override public HttpStatus getHttpStatus() { return httpStatus; diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/event/CreateDefenseMessageEvent.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/event/CreateDefenseMessageEvent.java new file mode 100644 index 00000000..3613dd52 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/event/CreateDefenseMessageEvent.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.defense_management.domain.event; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class CreateDefenseMessageEvent { + + private final Long defenseSessionId; +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java index 2545be6b..53c491e2 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java @@ -51,8 +51,14 @@ public class DefenseSession extends BaseEntity { private static final Long INITIAL_ACCESS_PROBLEM_NUMBER = 1L; - public void terminateDefense(SessionService sessionService) { - sessionService.terminateDefense(this.getDefenseSessionId()); + public void validateSessionOwner(Long memberId) { + if (!this.member.getMemberId().equals(memberId)) { + throw new MorandiException(SessionErrorCode.INVALID_SESSION_OWNER); + } + } + + public boolean isTerminated() { + return examStatus == ExamStatus.COMPLETED; } public boolean terminateSession() { @@ -87,6 +93,7 @@ public void tryMoreProblem(Long problemNumber, LocalDateTime accessDateTime) { lastAccessDateTime = accessDateTime; } + public static DefenseSession startSession(Member member, Long recordId, DefenseType defenseType, Set problemNumbers, LocalDateTime startDateTime, LocalDateTime endDateTime) { return new DefenseSession(member, recordId, defenseType, problemNumbers, startDateTime, endDateTime); diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java new file mode 100644 index 00000000..beeeaa1c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java @@ -0,0 +1,146 @@ +package kr.co.morandi.backend.defense_management.infrastructure.adapter.defensemessaging; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.application.port.out.defensemessaging.DefenseMessagePort; +import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +@Component +public class DefenseMessageSseAdapter implements DefenseMessagePort { + + private final Map emitters = new ConcurrentHashMap<>(); + private static final Long DEFAULT_TIMEOUT = 60 * 60 * 24L; + + /* + * createConnection 메서드는 defenseSessionId를 받아서 해당 defenseSessionId에 해당하는 SseEmitter를 생성합니다. + * 이는 시험을 시작할 때 이벤트를 받았을 때 이 메서드를 실행하여 SseEmitter를 생성합니다. + * */ + @Override + public void createConnection(Long defenseSessionId) { + SseEmitter emitter = createSseEmitter(defenseSessionId); + emitters.put(defenseSessionId, emitter); + } + + /* + * getConnection 메서드에서는 defenseSessionId를 받아서 해당 defenseSessionId에 해당하는 SseEmitter를 반환합니다. + * send를 실패할 경우 SseEmitter를 다시 만드는 재시도 로직을 구현했습니다. (retryConnection) + * 재귀 호출로 최대 3번까지 재시도 합니다. + * */ + @Override + public SseEmitter getConnection(Long defenseSessionId) { + emitters.putIfAbsent(defenseSessionId, createSseEmitter(defenseSessionId)); + + + final SseEmitter sseEmitter = emitters.get(defenseSessionId); + + // init 메세지 전송 + try { + sseEmitter.send(SseEmitter.event() + .name("init") + .data(defenseSessionId) + ); + } catch (IOException e) { + emitters.remove(defenseSessionId); + + // 재시도 로직 추가 + return retryConnection(defenseSessionId, 3); + } + + return emitters.get(defenseSessionId); + } + + + /* + * 메세지 보낼 떄 사용하면 됩니다. defenseSessionId를 기준으로 SseEmitter를 찾아서 해당 SseEmitter에 message를 전송합니다. + * (message에 직렬화하여 전송) + * 성공적으로 전송되면 true를 반환하고, 실패하면 false를 반환합니다. + * */ + @Override + public boolean sendMessage(Long defenseSessionId, String message) { + SseEmitter emitter = emitters.get(defenseSessionId); + + if (emitter != null) { + try { + emitter.send(SseEmitter.event() + .name("message") + .data(message) + ); + return true; + } catch (Exception e) { + emitters.remove(defenseSessionId); + } + } + + return false; + } + + // 임시로 ping 메세지 전송 + @Scheduled(fixedRate = 5000) + public void checkConnection() { + emitters.forEach((k, emitter) -> { + try { + emitter.send(SseEmitter.event() + .name("ping") + .data("ping") + .reconnectTime(3000L) + ); + } + catch (Exception e) { + emitters.remove(k); + } + + }); + } + private SseEmitter createSseEmitter(Long defenseSessionId) { + SseEmitter emitter = new SseEmitter(DEFAULT_TIMEOUT); + + emitter.onTimeout(() -> { + emitters.remove(defenseSessionId); + }); + + + emitter.onCompletion(() -> { + emitters.remove(defenseSessionId); + emitter.complete(); + }); + + emitter.onError((e) -> { + emitters.remove(defenseSessionId); + }); + + return emitter; + } + + /** + * 재시도 로직을 구현했습니다. + * 재귀 호출로 최대 3번까지 재시도 + */ + private SseEmitter retryConnection(Long defenseSessionId, int count) { + if(count == 0) { + throw new MorandiException(SessionErrorCode.SESSION_CONNECTION_FAIL); + } + emitters.putIfAbsent(defenseSessionId, createSseEmitter(defenseSessionId)); + + final SseEmitter sseEmitter = emitters.get(defenseSessionId); + + try { + sseEmitter.send(SseEmitter.event() + .name("init") + .data(defenseSessionId) + ); + } catch (IOException e) { + emitters.remove(defenseSessionId); + + return retryConnection(defenseSessionId, count - 1); + } + return sseEmitter; + } + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java index 4f3a4612..1d793a78 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/DefenseMangementController.java @@ -3,7 +3,7 @@ import jakarta.validation.Valid; import kr.co.morandi.backend.common.web.MemberId; import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; -import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; +import kr.co.morandi.backend.defense_management.application.usecase.session.DailyDefenseManagementUsecase; import kr.co.morandi.backend.defense_management.infrastructure.request.dailydefense.StartDailyDefenseRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -16,13 +16,13 @@ @RequiredArgsConstructor public class DefenseMangementController { - private final DailyDefenseManagementService dailyDefenseManagementService; + private final DailyDefenseManagementUsecase dailyDefenseManagementUsecase; @PostMapping public ResponseEntity startDailyDefense(@MemberId Long memberId, @Valid @RequestBody StartDailyDefenseRequest request) { - return ResponseEntity.ok(dailyDefenseManagementService + return ResponseEntity.ok(dailyDefenseManagementUsecase .startDailyDefense(request.toServiceRequest(), memberId, LocalDateTime.now()) ); } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionController.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionController.java new file mode 100644 index 00000000..131ea5d7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionController.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.defense_management.infrastructure.controller; + +import kr.co.morandi.backend.common.web.MemberId; +import kr.co.morandi.backend.defense_management.application.service.message.DefenseMessageService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +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 org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@RestController +@RequestMapping("/session") +@RequiredArgsConstructor +public class SessionConnectionController { + + private final DefenseMessageService defenseMessageService; + + @GetMapping(value = "/{sessionId}/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter connectSession(@PathVariable Long sessionId, @MemberId Long memberId) { + + return defenseMessageService.getConnection(sessionId, memberId); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/service/DailyRecordRankUseCaseImpl.java b/src/main/java/kr/co/morandi/backend/defense_record/usecase/DailyRecordRankUseCaseImpl.java similarity index 97% rename from src/main/java/kr/co/morandi/backend/defense_record/application/service/DailyRecordRankUseCaseImpl.java rename to src/main/java/kr/co/morandi/backend/defense_record/usecase/DailyRecordRankUseCaseImpl.java index dfd27164..ec27406a 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/application/service/DailyRecordRankUseCaseImpl.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/usecase/DailyRecordRankUseCaseImpl.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.defense_record.application.service; +package kr.co.morandi.backend.defense_record.usecase; import kr.co.morandi.backend.defense_record.application.dto.DailyDefenseRankPageResponse; import kr.co.morandi.backend.defense_record.application.dto.DailyDetailRankResponse; diff --git a/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java index 815d75c2..6cd0b8a8 100644 --- a/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java +++ b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java @@ -2,6 +2,8 @@ import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; import kr.co.morandi.backend.defense_information.infrastructure.controller.DailyDefenseController; +import kr.co.morandi.backend.defense_management.application.service.message.DefenseMessageService; +import kr.co.morandi.backend.defense_management.infrastructure.controller.SessionConnectionController; import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; import kr.co.morandi.backend.defense_record.infrastructure.controller.DailyRecordController; import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; @@ -17,7 +19,8 @@ @WebMvcTest(controllers = { DailyDefenseController.class, - DailyRecordController.class + DailyRecordController.class, + SessionConnectionController.class }, excludeAutoConfiguration = SecurityAutoConfiguration.class, excludeFilters = { @@ -32,13 +35,19 @@ public abstract class ControllerTestSupport { @Autowired protected MockMvc mockMvc; + // DailyDefenseController @MockBean protected DailyDefenseUseCase dailyDefenseUseCase; @MockBean protected CookieUtils cookieUtils; + // DailyRecordController @MockBean protected DailyRecordRankUseCase dailyRecordRankUseCase; + //SessionConnectionController + @MockBean + protected DefenseMessageService defenseMessageService; + } diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java new file mode 100644 index 00000000..e2d5413f --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java @@ -0,0 +1,150 @@ +package kr.co.morandi.backend.defense_management.application.service.message; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; +import kr.co.morandi.backend.defense_management.application.usecase.session.DailyDefenseManagementUsecase; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@Transactional +class DefenseMessageServiceTest extends IntegrationTestSupport { + + @Autowired + private DefenseMessageService defenseMessageService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyDefenseManagementUsecase dailyDefenseManagementService; + + @Autowired + private DefenseSessionRepository defenseSessionRepository; + + + @DisplayName("다른 사람의 디펜스 세션에 연결을 요청하면 예외가 발생한다.") + @Test + void getConnectionToOtherUserDefense() { + // given + Member 시험_시작_사용자 = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + LocalDateTime 요청_시각 = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest 시험_시작_요청 = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + final StartDailyDefenseResponse 오늘의_문제_응답 = dailyDefenseManagementService.startDailyDefense(시험_시작_요청, 시험_시작_사용자.getMemberId(), 요청_시각); + + Long 디펜스_세션_아이디 = 오늘의_문제_응답.getDefenseSessionId(); + + Long 다른_사용자_아이디 = 100L; + + // when & then + assertThatThrownBy(() -> defenseMessageService.getConnection(디펜스_세션_아이디, 다른_사용자_아이디)) + .isInstanceOf(MorandiException.class) + .hasMessage("사용자의 시험 세션이 아닙니다."); + + } + + @DisplayName("시작된 디펜스에 연결을 요청하면 성공한다.") + @Test + void getConnection() { + // given + Member 시험_시작_사용자 = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime 요청_시각 = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest 시험_시작_요청 = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + final StartDailyDefenseResponse 오늘의_문제_응답 = dailyDefenseManagementService.startDailyDefense(시험_시작_요청, 시험_시작_사용자.getMemberId(), 요청_시각); + + Long 디펜스_세션_아이디 = 오늘의_문제_응답.getDefenseSessionId(); + + // when + final SseEmitter 연결 = defenseMessageService.getConnection(디펜스_세션_아이디, 시험_시작_사용자.getMemberId()); + + // then + assertThat(연결).isNotNull(); + } + + @DisplayName("이미 종료된 디펜스에 연결을 요청하면 성공한다.") + @Test + void getConnectionToTerminatedDefense() { + // given + Member 시험_시작_사용자 = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime 요청_시각 = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest 시험_시작_요청 = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + final StartDailyDefenseResponse 오늘의_문제_응답 = dailyDefenseManagementService.startDailyDefense(시험_시작_요청, 시험_시작_사용자.getMemberId(), 요청_시각); + + Long 디펜스_세션_아이디 = 오늘의_문제_응답.getDefenseSessionId(); + + DefenseSession 디펜스_세션 = defenseSessionRepository.findById(디펜스_세션_아이디).get(); + 디펜스_세션.terminateSession(); + defenseSessionRepository.save(디펜스_세션); + + + // when & then + assertThatThrownBy(() -> defenseMessageService.getConnection(디펜스_세션_아이디, 시험_시작_사용자.getMemberId())) + .isInstanceOf(MorandiException.class) + .hasMessage("이미 종료된 디펜스 세션입니다."); + + } + + private DailyDefense createDailyDefense(LocalDate createdDate, String contentName) { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p-> problemNumber.getAndIncrement(), problem -> problem)); + return dailyDefenseRepository.save(DailyDefense.create(createdDate, contentName, problemMap)); + } + private List createProblems() { + Problem problem1 = Problem.create(1000L, B5, 0L); + Problem problem2 = Problem.create(2000L, S5, 0L); + Problem problem3 = Problem.create(3000L, G5, 0L); + + return problemRepository.saveAll(List.of(problem1, problem2, problem3)); + } + private Member createMember() { + return memberRepository.save(Member.create("test", "test" + "@gmail.com", GOOGLE, "test", "test")); + } + + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/usecase/dailydefense/DailyDefenseManagementUsecaseTest.java similarity index 79% rename from src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java rename to src/test/java/kr/co/morandi/backend/defense_management/application/usecase/dailydefense/DailyDefenseManagementUsecaseTest.java index a9569415..07a75f4b 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/application/service/dailydefense/DailyDefenseManagementServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/usecase/dailydefense/DailyDefenseManagementUsecaseTest.java @@ -1,14 +1,16 @@ -package kr.co.morandi.backend.defense_management.application.service.dailydefense; +package kr.co.morandi.backend.defense_management.application.usecase.dailydefense; import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDetailRepository; +import kr.co.morandi.backend.defense_management.application.port.out.defensemessaging.DefenseMessagePort; import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; -import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; import kr.co.morandi.backend.defense_management.application.service.timer.DefenseTimerService; +import kr.co.morandi.backend.defense_management.application.usecase.session.DailyDefenseManagementUsecase; +import kr.co.morandi.backend.defense_management.domain.event.CreateDefenseMessageEvent; import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.DefenseSessionRepository; import kr.co.morandi.backend.defense_management.infrastructure.persistence.session.SessionDetailRepository; @@ -49,10 +51,10 @@ @RecordApplicationEvents -class DailyDefenseManagementServiceTest extends IntegrationTestSupport { +class DailyDefenseManagementUsecaseTest extends IntegrationTestSupport { @Autowired - private DailyDefenseManagementService dailyDefenseManagementService; + private DailyDefenseManagementUsecase dailyDefenseManagementUsecase; @Autowired private MemberRepository memberRepository; @@ -90,6 +92,9 @@ class DailyDefenseManagementServiceTest extends IntegrationTestSupport { @MockBean private DefenseTimerService defenseTimerService; + @MockBean + private DefenseMessagePort defenseMessagePort; + @BeforeEach void setUp() { Map problemContentMap = Map.of( @@ -122,6 +127,66 @@ void tearDown() { memberRepository.deleteAllInBatch(); } + @DisplayName("오늘의 문제가 시작되다가 롤백되면 Sse연결 이벤트가 실행되지 않는다.") + @Test + void eventPublishWhenStartDailyDefenseWhenRollbackSse() { + // given + Member 시험_시작_사용자 = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime 요청_시각 = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest 시험_시작_요청 = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + + // when + transactionTemplate.execute(status -> { + dailyDefenseManagementUsecase.startDailyDefense(시험_시작_요청, 시험_시작_사용자.getMemberId(), 요청_시각); + status.setRollbackOnly(); // Force rollback + return null; + }); + + // then + assertThat(applicationEvents.stream(CreateDefenseMessageEvent.class)) + .hasSize(1) + .anySatisfy(event -> { + assertAll( + () -> assertThat(event.getDefenseSessionId()).isNotNull() + ); + }); + + verify(defenseMessagePort, never()).createConnection(any()); + } + + @DisplayName("오늘의 문제가 시작되면 Sse연결 이벤트가 실행된다.") + @Test + void eventPublishWhenStartDailyDefenseSse() { + // given + Member 시험_시작_사용자 = createMember(); + createDailyDefense(LocalDate.of(2021, 10, 1), "오늘의 문제 테스트"); + + LocalDateTime 요청_시각 = LocalDateTime.of(2021, 10, 1, 0, 0, 0); + + StartDailyDefenseServiceRequest 시험_시작_요청 = StartDailyDefenseServiceRequest.builder() + .problemNumber(2L) + .build(); + + // when + dailyDefenseManagementUsecase.startDailyDefense(시험_시작_요청, 시험_시작_사용자.getMemberId(), 요청_시각); + + // then + assertThat(applicationEvents.stream(CreateDefenseMessageEvent.class)) + .hasSize(1) + .anySatisfy(event -> { + assertAll( + () -> assertThat(event.getDefenseSessionId()).isNotNull() + ); + }); + + verify(defenseMessagePort, times(1)).createConnection(any()); + } + @DisplayName("오늘의 문제가 시작되다가 롤백되면 타이머 이벤트가 실행되지 않는다.") @Test void eventPublishWhenStartDailyDefenseWhenRollback() { @@ -137,7 +202,7 @@ void eventPublishWhenStartDailyDefenseWhenRollback() { // when transactionTemplate.execute(status -> { - dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + dailyDefenseManagementUsecase.startDailyDefense(request, member.getMemberId(), requestTime); status.setRollbackOnly(); // Force rollback return null; }); @@ -170,7 +235,7 @@ void eventPublishWhenStartDailyDefense() { .build(); // when - dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + dailyDefenseManagementUsecase.startDailyDefense(request, member.getMemberId(), requestTime); // then assertThat(applicationEvents.stream(DefenseStartTimerEvent.class)) @@ -197,7 +262,7 @@ void retryDailyDefenseWhenDayPassed() { StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() .problemNumber(1L) .build(); - dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + dailyDefenseManagementUsecase.startDailyDefense(request, member.getMemberId(), requestTime); StartDailyDefenseServiceRequest retryRequest = StartDailyDefenseServiceRequest.builder() .problemNumber(2L) @@ -206,7 +271,7 @@ void retryDailyDefenseWhenDayPassed() { LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 2, 12, 0, 0); // when - final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member.getMemberId(), retryRequestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementUsecase.startDailyDefense(retryRequest, member.getMemberId(), retryRequestTime); // then @@ -236,7 +301,7 @@ void retryDailyDefenseWithOtherProblem() { StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() .problemNumber(1L) .build(); - dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + dailyDefenseManagementUsecase.startDailyDefense(request, member.getMemberId(), requestTime); StartDailyDefenseServiceRequest retryRequest = StartDailyDefenseServiceRequest.builder() .problemNumber(2L) @@ -244,7 +309,7 @@ void retryDailyDefenseWithOtherProblem() { LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); // when - final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(retryRequest, member.getMemberId(), retryRequestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementUsecase.startDailyDefense(retryRequest, member.getMemberId(), retryRequestTime); // then assertAll( @@ -273,12 +338,12 @@ void retryDailyDefense() { StartDailyDefenseServiceRequest request = StartDailyDefenseServiceRequest.builder() .problemNumber(2L) .build(); - dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + dailyDefenseManagementUsecase.startDailyDefense(request, member.getMemberId(), requestTime); LocalDateTime retryRequestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); // when - final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), retryRequestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementUsecase.startDailyDefense(request, member.getMemberId(), retryRequestTime); // then assertAll( @@ -308,7 +373,7 @@ void startDailyDefense() { .build(); // when - final StartDailyDefenseResponse response = dailyDefenseManagementService.startDailyDefense(request, member.getMemberId(), requestTime); + final StartDailyDefenseResponse response = dailyDefenseManagementUsecase.startDailyDefense(request, member.getMemberId(), requestTime); // then diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java index 2fa35445..8b96f2ec 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java @@ -24,9 +24,52 @@ import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.CPP; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ActiveProfiles("test") class DefenseSessionTest { + + @DisplayName("세션 소유자일 경우 아무 예외가 발생하지 않는다.") + @Test + void validateSessionOwner() { + // given + final Member member = mock(Member.class); + when(member.getMemberId()) + .thenReturn(1L); + + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + Map problems = getProblems(dailyDefense, 1L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + + // when & then + defenseSession.validateSessionOwner(member.getMemberId()); + } + + @DisplayName("세션 소유자가 아닐 경우 예외를 발생한다.") + @Test + void throwWhenNotSessionOwner() { + // given + final Member member = mock(Member.class); + when(member.getMemberId()) + .thenReturn(1L); + LocalDateTime startTime = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + DailyDefense dailyDefense = createDailyDefense(startTime.toLocalDate()); + Map problems = getProblems(dailyDefense, 1L); + DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); + + DefenseSession defenseSession = DefenseSession.startSession(member, dailyRecord.getRecordId(), dailyDefense.getDefenseType(), problems.keySet(), startTime, dailyDefense.getEndTime(startTime)); + + + // when & then + assertThatThrownBy(() -> defenseSession.validateSessionOwner(2L)) + .isInstanceOf(MorandiException.class) + .hasMessage("사용자의 시험 세션이 아닙니다."); + } @DisplayName("세션을 종료할 수 있다.") @Test void terminateSession() { @@ -224,7 +267,11 @@ void startSession() { } private Member createMember() { - return Member.create("nickname", "email", SocialType.GOOGLE, "imageURL", "description"); + return Member.builder() + .nickname("nickname") + .email("email") + .socialType(SocialType.GOOGLE) + .build(); } private DailyDefense createDailyDefense(LocalDate createdDate) { AtomicLong problemNumber = new AtomicLong(1L); diff --git a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionControllerTest.java b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionControllerTest.java new file mode 100644 index 00000000..5ae84942 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/SessionConnectionControllerTest.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.defense_management.infrastructure.controller; + +import kr.co.morandi.backend.ControllerTestSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class SessionConnectionControllerTest extends ControllerTestSupport { + + @DisplayName("[GET] 메세지를 받기 위한 SSE를 연결한다.") + @WithMockUser + @Test + void connectSession() throws Exception { + Long 세션_아이디 = 1L; + + SseEmitter expectedEmitter = new SseEmitter(); + expectedEmitter.send("test"); + + when(defenseMessageService.getConnection(anyLong(), anyLong())) + .thenReturn(expectedEmitter); + + mockMvc.perform(get("/session/{sessionId}/connect", 세션_아이디) + .accept(MediaType.TEXT_EVENT_STREAM)) + .andExpect(status().isOk()); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/application/DailyRecordRankUseCaseTest.java b/src/test/java/kr/co/morandi/backend/defense_record/application/usecase/DailyRecordRankUseCaseTest.java similarity index 98% rename from src/test/java/kr/co/morandi/backend/defense_record/application/DailyRecordRankUseCaseTest.java rename to src/test/java/kr/co/morandi/backend/defense_record/application/usecase/DailyRecordRankUseCaseTest.java index 6ab76909..a27cec36 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/application/DailyRecordRankUseCaseTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/application/usecase/DailyRecordRankUseCaseTest.java @@ -1,4 +1,4 @@ -package kr.co.morandi.backend.defense_record.application; +package kr.co.morandi.backend.defense_record.application.usecase; import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; diff --git a/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java b/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java index e35b275d..6becb169 100644 --- a/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java +++ b/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java @@ -1,33 +1,33 @@ -package kr.co.morandi.backend.docs; + package kr.co.morandi.backend.docs; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; + import com.fasterxml.jackson.databind.ObjectMapper; + import com.fasterxml.jackson.databind.SerializationFeature; + import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + import org.junit.jupiter.api.BeforeEach; + import org.junit.jupiter.api.extension.ExtendWith; + import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; + import org.springframework.restdocs.RestDocumentationContextProvider; + import org.springframework.restdocs.RestDocumentationExtension; + import org.springframework.test.web.servlet.MockMvc; + import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; + import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -@ExtendWith(RestDocumentationExtension.class) -public abstract class RestDocsSupport { + @ExtendWith(RestDocumentationExtension.class) + public abstract class RestDocsSupport { - protected MockMvc mockMvc; - protected ObjectMapper objectMapper = new ObjectMapper() - .registerModule(new JavaTimeModule()) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + protected MockMvc mockMvc; + protected ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - @BeforeEach - void setUp(RestDocumentationContextProvider provider) { - this.mockMvc = MockMvcBuilders.standaloneSetup(initController()) - .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper)) - .apply(documentationConfiguration(provider)) - .build(); - } + @BeforeEach + void setUp(RestDocumentationContextProvider provider) { + this.mockMvc = MockMvcBuilders.standaloneSetup(initController()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper)) + .apply(documentationConfiguration(provider)) + .build(); + } - protected abstract Object initController(); -} \ No newline at end of file + protected abstract Object initController(); + } \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java index 40b3392a..9d460330 100644 --- a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java +++ b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyRecordControllerDocsTest.java @@ -54,6 +54,7 @@ void getDailyRecordRank() throws Exception { .build()); } + // 각 유저가 5개의 문제에 대한 세부 정보를 갖도록 설정 List records = new ArrayList<>(); for (long i = 1; i <= 5; i++) { // 5명의 유저 diff --git a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java index 01909971..91f817dc 100644 --- a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java +++ b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DefenseManagementControllerDocsTest.java @@ -4,7 +4,7 @@ import kr.co.morandi.backend.defense_management.application.response.session.DefenseProblemResponse; import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; import kr.co.morandi.backend.defense_management.application.response.tempcode.TempCodeResponse; -import kr.co.morandi.backend.defense_management.application.service.session.DailyDefenseManagementService; +import kr.co.morandi.backend.defense_management.application.usecase.session.DailyDefenseManagementUsecase; import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; import kr.co.morandi.backend.defense_management.infrastructure.controller.DefenseMangementController; import kr.co.morandi.backend.defense_management.infrastructure.request.dailydefense.StartDailyDefenseRequest; @@ -33,7 +33,7 @@ public class DefenseManagementControllerDocsTest extends RestDocsSupport { - private final DailyDefenseManagementService dailyDefenseManagementService = mock(DailyDefenseManagementService.class); + private final DailyDefenseManagementUsecase dailyDefenseManagementService = mock(DailyDefenseManagementUsecase.class); @Override protected Object initController() { diff --git a/src/test/java/kr/co/morandi/backend/docs/dailydefense/SessionConnectionControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/dailydefense/SessionConnectionControllerDocsTest.java new file mode 100644 index 00000000..78afbc73 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/docs/dailydefense/SessionConnectionControllerDocsTest.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend.docs.dailydefense; + +import kr.co.morandi.backend.defense_management.application.service.message.DefenseMessageService; +import kr.co.morandi.backend.defense_management.infrastructure.controller.SessionConnectionController; +import kr.co.morandi.backend.docs.RestDocsSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +public class SessionConnectionControllerDocsTest extends RestDocsSupport { + + private final DefenseMessageService defenseMessageService = mock(DefenseMessageService.class); + @Override + protected Object initController() { + return new SessionConnectionController(defenseMessageService); + } + + @DisplayName("SSE 세션을 연결하는 API") + @Test + void connectSession() throws Exception { + Long 세션_아이디 = 1L; + + SseEmitter expectedEmitter = new SseEmitter(); + + when(defenseMessageService.getConnection(anyLong(), anyLong())) + .thenReturn(expectedEmitter); + + + mockMvc.perform(get("/session/{sessionId}/connect", 세션_아이디) + .accept(MediaType.TEXT_EVENT_STREAM)) + .andDo(document("connect-session", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )); + } +} From 24bdfc58810553fac2e1ad204f420e578a8426c0 Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:50:49 +0900 Subject: [PATCH 13/14] =?UTF-8?q?=EB=B0=B1=EC=A4=80=20=EC=A0=9C=EC=B6=9C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: 백준 제출 기능 구현 및 테스트 작성 * chore: private 생성자 생성 * :recycle: 의존관계 리팩토링 * :recycle: 상수 클래스 이동 * feat: 백준 쿠키 관리 서비스 뼈대 잡아두기 * :sparkles: SolutionId로 pusher 구독 구현 * :sparkles: 채점 응답 파싱 기능 추가 * :recycle: Judgement 관련 패키지 분리 * :recycle: 테스트코드 패키지 변경 * :white_check_mark: BaekjoonSubmitLanguageCodeTest 추가 * :sparkles: 제출 시 저장 엔티티 구현 및 테스트 코드 작성 * :sparkles: 제출 엔티티 완성 | SuperBuilder 제거 후 Builder로 변경 * test: TestConfiguration으로 mocking한 WebClient사용하도록 Test Context 변경 * test: teardown method을 transactional로 변경 * :art: TrialNumber submit 부모로 이동 및 테스트 * :recycle: Submit 패키지 구조 변경 * :art: Submit -> 구체 사용하도록 변경함 * chore: sout, log 제거 * :art: 프로젝트 전체에서 Record to Record로 변경 * :sparkles: 채점 및 judge 1차 구현 및 컨트롤러 개발 * test: BaekjoonJudgementResult 정적 팩토리 메서드 테스트 추가 * chore: Judgement Status 업데이트 시 로그 추가 * chore: Subscriber에서 고수준 의존에 대해 TODO comment 추가 * test: SubmitVisibility Test 추가 * test: JudgementResult Test 추가 * :art: ResultType을 BaekjoonResultType으로 변경 후 테스트 코드 작성 * test: BaekjoonSubmitHtmlParser Test 추가 * refactor: BaekjoonSubmitHtmlParser static 메서드로 변경 * feat: TempCode 저장 이벤트 리스너 구현 * test: 테스트에 누락된 Transactional 추가하여 클렌징 * refactor: SessionDetail 메서드 로직 리팩토링 * Rename: TempCodeService를 TempCodeSaveService로 바꿈 * feat: 각 작업별로 스레드풀을 분리하여 한 외부 작업의 가용성에 의해 다른 작업이 영향받지 않도록 수정 * chore: 제출 비동기로 처리한 이유에 관한 주석 추가 * chore: remove unused import & CustomDefense builder 추가 * chore: remove unused SuperBuilder import * rename: record 예약어 상황에 맞게 foundRecord로 변경 * chore: remove unused import * chore: remove unused import * chore: remove unused import * chore: remove unused import * refactor: TempCodeSaveEvent record로 변경 * chore: 기본 생성자 Private 변경 * refactor: Column명 변경 * :sparkles: 사용자 쿠키가 만료되지 않았을 경우 반환하는 로직 생성 * :sparkles: 쿠키 비활성화 enum 추가 및 테스트 추가 * rename: BaekjoonCookieManager로 클래스명 변경 * feat: 백준 글로벌 쿠키 refreshToken필드 추가 및 테스트 작성 * test: BaekjoonJudgementStatus 테스트 추가 * :sparkles: GetCurrentMemberCookie 기능 개발 및 fallback 로직 구현 * refactor: 리턴 타입 제네릭 Void로 변경 * rename: submitService SubmitFacade로 이름 변경 * feat: member cookie 저장 controller 구현 * rename: 메서드 명 변경 * refactor: member에 cookie 연관관계 추가 및 테스트 작성 * test: BaekjoonMemberCookieService Test * test: CookieController Test 작성 * docs: CookieController RestDocs 추가 * rename: submitCode 클래스 sourceCode로 변경 * test: sourceCode 변경으로 발생하는 테스트 수정 * refactor: tempCode에서 코드 저장 시 Embedded 타입인 SourceCode 사용하도록 수정 * feat: submit에 제출 시간 추가 및 trailNumber 삭제 * chore: BaekjoonSubmitStrategy에서 now -> nowDateTime 변경 * refactor: submit을 통해 detail의 정답 제출을 지정할 수 있게 리팩토링 및 테스트 수정 * refactor: 채점 완료 후 업데이트 시 fetch join 사용하도록 변경 * feat: submit 생성자에서 detail의 submitCount 증가하게 변경 및 테스트 작성 * fix: TransactionalEventListener에 fallbackExecution 옵션 추가 * test: submit 테스트 작성 * test: BaekjoonCookie test 작성 * test: SubmitController test 작성 * test: SubmitController RestDocs 작성 * test: CookieController RestDocs request field 설명 추가 * refactor: 생성자 validation inline으로 수정 * refactor: 백준 쿠키 생성자 validation inline으로 수정 * chore: recordAdapter 어노테이션 변경 및 파라미터 이름 변경 --- build.gradle | 6 + src/docs/asciidoc/api/cookie/cookie.adoc | 11 + src/docs/asciidoc/api/submit/submit.adoc | 10 + src/docs/asciidoc/index.adoc | 4 + .../backend/common/annotation/Adapter.java | 17 + .../backend/common/annotation/Usecase.java | 17 + .../backend/common/config/AsyncConfig.java | 48 ++ .../handler/GlobalExceptionHandler.java | 18 + .../backend/common/model/BaseEntity.java | 2 - .../backend/common/web/response/ApiUtils.java | 51 ++ .../model/customdefense/CustomDefense.java | 8 +- .../model/dailydefense/DailyDefense.java | 4 +- .../domain/model/defense/Defense.java | 2 - .../randomdefense/model/RandomDefense.java | 14 +- .../stagedefense/model/StageDefense.java | 12 +- .../port/out/session/DefenseSessionPort.java | 1 + .../domain/error/LanguageErrorCode.java | 25 + .../domain/model/session/DefenseSession.java | 10 +- .../domain/model/session/SessionDetail.java | 39 +- .../domain/model/tempcode/model/Language.java | 25 +- .../domain/model/tempcode/model/TempCode.java | 25 +- .../domain/service/DefenseEventService.java | 2 +- .../domain/service/SessionService.java | 7 +- .../domain/service/TempCodeSaveService.java | 39 ++ .../session/DefenseSessionAdapter.java | 5 + .../session/DefenseSessionRepository.java | 9 + .../port/out/record/RecordPort.java | 7 +- .../domain/error/RecordErrorCode.java | 4 +- .../customdefense_record/CustomDetail.java | 18 +- .../customdefense_record/CustomRecord.java | 11 +- .../dailydefense_record/DailyDetail.java | 18 +- .../dailydefense_record/DailyRecord.java | 31 +- .../randomdefense_record/RandomDetail.java | 18 +- .../randomdefense_record/RandomRecord.java | 10 +- .../domain/model/record/Detail.java | 43 +- .../domain/model/record/Record.java | 25 +- .../domain/model/record/SubmitCode.java | 32 - .../stagedefense_record/StageDetail.java | 17 +- .../stagedefense_record/StageRecord.java | 6 +- .../adapter/record/RecordAdapter.java | 23 +- .../DailyRecordRepository.java | 2 +- .../persistence/record/RecordRepository.java | 23 +- .../port/out/BaekjoonSubmitPort.java | 11 + .../request/JudgementServiceRequest.java | 35 ++ .../BaekjoonMemberCookieServiceRequest.java | 10 + .../application/service/SubmitFacade.java | 43 ++ .../application/service/SubmitStrategy.java | 11 + .../cookie/BaekjoonCookieManager.java | 64 ++ .../cookie/BaekjoonMemberCookieService.java | 35 ++ .../result/BaekjoonJudgementStatus.java | 79 +++ .../result/JudgementResultSubscriber.java | 82 +++ .../submit/BaekjoonSubmitStrategy.java | 37 ++ .../usecase/submit/BaekjoonSubmitUsecase.java | 105 ++++ .../domain/error/BaekjoonCookieErrorCode.java | 34 ++ .../error/JudgementResultErrorCode.java | 56 ++ .../domain/error/SubmitErrorCode.java | 55 ++ .../domain/event/TempCodeSaveEvent.java | 12 + .../model/baekjoon/cookie/BaekjoonCookie.java | 83 +++ .../baekjoon/cookie/BaekjoonGlobalCookie.java | 57 ++ .../baekjoon/cookie/BaekjoonMemberCookie.java | 49 ++ .../model/baekjoon/cookie/CookieStatus.java | 5 + .../result/BaekjoonJudgementResult.java | 76 +++ .../baekjoon/result/BaekjoonResultType.java | 45 ++ .../model/baekjoon/submit/BaekjoonSubmit.java | 44 ++ .../domain/model/submit/JudgementResult.java | 76 +++ .../domain/model/submit/JudgementStatus.java | 38 ++ .../domain/model/submit/SourceCode.java | 45 ++ .../judgement/domain/model/submit/Submit.java | 89 +++ .../domain/model/submit/SubmitVisibility.java | 39 ++ .../service/BaekjoonJudgementService.java | 55 ++ .../adapter/out/BaekjoonSubmitAdapter.java | 25 + .../cookie/MorandiBaekjoonCookieManager.java | 19 + .../baekjoon/result/PusherService.java | 36 ++ .../submit/BaekjoonSubmitApiAdapter.java | 112 ++++ .../submit/BaekjoonSubmitConstants.java | 9 + .../submit/BaekjoonSubmitHtmlParser.java | 60 ++ .../submit/BaekjoonSubmitLanguageCode.java | 49 ++ .../submit/BaekjoonSubmitVisibility.java | 19 + .../infrastructure/config/PusherConfig.java | 22 + .../controller/BaekjoonSubmitController.java | 27 + .../cookie/BaekjoonMemberCookieRequest.java | 15 + .../controller/cookie/CookieController.java | 28 + .../request/BaekjoonJudgementRequest.java | 54 ++ .../helper/JudgementStatusMapper.java | 22 + .../BaekjoonGlobalCookieRepository.java | 18 + .../BaekjoonMemberCookieRepository.java | 10 + .../submit/BaekjoonSubmitRepository.java | 21 + .../port/out/member/MemberPort.java | 1 + .../domain/model/error/MemberErrorCode.java | 23 + .../domain/model/member/Member.java | 19 + .../adapter/member/MemberAdapter.java | 4 + .../persistence/member/MemberRepository.java | 9 + .../backend/ControllerTestSupport.java | 21 +- .../backend/IntegrationTestSupport.java | 6 + .../backend/config/WebClientTestConfig.java | 33 + .../service/DailyDefenseUseCaseImplTest.java | 7 +- .../model/dailydefense/DailyDetailTest.java | 6 +- .../model/randomdefense/RandomDetailTest.java | 6 +- .../model/stagedefense/StageDetailTest.java | 6 +- .../DailyDefenseGenerationServiceTest.java | 20 +- .../message/DefenseMessageServiceTest.java | 34 ++ .../JudgementResultSubscriberTest.java | 29 + .../model/session/DefenseSessionTest.java | 81 +++ .../model/tempcode/model/LanguageTest.java | 63 ++ .../domain/service/SessionServiceTest.java | 6 +- .../service/TempCodeSaveServiceTest.java | 7 + .../session/DefenseSessionRepositoryTest.java | 97 +++ .../usecase/DailyRecordRankUseCaseTest.java | 24 +- .../CustomRecordTest.java | 6 +- .../dailydefense_record/DailyRecordTest.java | 29 +- .../RandomRecordTest.java | 6 +- .../domain/model/record/RecordTest.java | 152 +++++ .../stagedefense_record/StageRecordTest.java | 4 +- .../adapter/record/RecordAdapterTest.java | 26 +- .../DailyRecordRepositoryTest.java | 37 +- .../docs/cookie/CookieControllerDocsTest.java | 54 ++ .../DailyDefenseControllerDocsTest.java | 2 +- .../BaekjoonSubmitControllerDocsTest.java | 78 +++ .../factory/TestBaekjoonSubmitFactory.java | 26 + .../backend/factory/TestDefenseFactory.java | 20 + .../backend/factory/TestMemberFactory.java | 14 + .../backend/factory/TestProblemFactory.java | 42 ++ .../BaekjoonMemberCookieServiceTest.java | 84 +++ .../result/BaekjoonJudgementStatusTest.java | 278 +++++++++ .../result/BaekjoonResultTypeTest.java | 117 ++++ .../baekjoon/cookie/BaekjoonCookieTest.java | 280 +++++++++ .../cookie/BaekjoonGlobalCookieTest.java | 106 ++++ .../cookie/BaekjoonMemberCookieTest.java | 237 +++++++ .../result/BaekjoonJudgementResultTest.java | 199 ++++++ .../baekjoon/result/JudgementResultTest.java | 175 ++++++ .../baekjoon/submit/BaekjoonSubmitTest.java | 117 ++++ .../domain/model/submit/SourceCodeTest.java | 57 ++ .../domain/model/submit/SubmitTest.java | 578 ++++++++++++++++++ .../service/BaekjoonJudgementServiceTest.java | 111 ++++ .../infrastructure/SubmitVisibilityTest.java | 121 ++++ .../submit/BaekjoonSubmitHtmlParserTest.java | 156 +++++ .../BaekjoonSubmitLanguageCodeTest.java | 58 ++ .../submit/BaekjoonSubmitStrategyTest.java | 222 +++++++ .../BaekjoonSubmitControllerTest.java | 307 ++++++++++ .../cookie/CookieControllerTest.java | 58 ++ .../BaekjoonGlobalCookieRepositoryTest.java | 52 ++ .../BaekjoonMemberCookieRepositoryTest.java | 55 ++ .../submit/BaekjoonSubmitRepositoryTest.java | 149 +++++ .../member/MemberRepositoryTest.java | 27 +- .../problem/ProblemRepositoryTest.java | 9 +- 145 files changed, 6898 insertions(+), 268 deletions(-) create mode 100644 src/docs/asciidoc/api/cookie/cookie.adoc create mode 100644 src/docs/asciidoc/api/submit/submit.adoc create mode 100644 src/main/java/kr/co/morandi/backend/common/annotation/Adapter.java create mode 100644 src/main/java/kr/co/morandi/backend/common/annotation/Usecase.java create mode 100644 src/main/java/kr/co/morandi/backend/common/config/AsyncConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/common/web/response/ApiUtils.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/domain/error/LanguageErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveService.java delete mode 100644 src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/SubmitCode.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/port/out/BaekjoonSubmitPort.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/request/JudgementServiceRequest.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/request/cookie/BaekjoonMemberCookieServiceRequest.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitFacade.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitStrategy.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonCookieManager.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieService.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatus.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/JudgementResultSubscriber.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/submit/BaekjoonSubmitStrategy.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/application/usecase/submit/BaekjoonSubmitUsecase.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/error/BaekjoonCookieErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/error/JudgementResultErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/error/SubmitErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/event/TempCodeSaveEvent.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookie.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookie.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookie.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/CookieStatus.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResult.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonResultType.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmit.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementResult.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementStatus.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCode.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/Submit.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitVisibility.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementService.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/adapter/out/BaekjoonSubmitAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/cookie/MorandiBaekjoonCookieManager.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/result/PusherService.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitApiAdapter.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitConstants.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParser.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCode.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitVisibility.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/config/PusherConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitController.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/BaekjoonMemberCookieRequest.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieController.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/request/BaekjoonJudgementRequest.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/helper/JudgementStatusMapper.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepository.java create mode 100644 src/main/java/kr/co/morandi/backend/member_management/domain/model/error/MemberErrorCode.java create mode 100644 src/test/java/kr/co/morandi/backend/config/WebClientTestConfig.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/domain/model/judgement/JudgementResultSubscriberTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/LanguageTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepositoryTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordTest.java create mode 100644 src/test/java/kr/co/morandi/backend/docs/cookie/CookieControllerDocsTest.java create mode 100644 src/test/java/kr/co/morandi/backend/docs/submit/BaekjoonSubmitControllerDocsTest.java create mode 100644 src/test/java/kr/co/morandi/backend/factory/TestBaekjoonSubmitFactory.java create mode 100644 src/test/java/kr/co/morandi/backend/factory/TestDefenseFactory.java create mode 100644 src/test/java/kr/co/morandi/backend/factory/TestMemberFactory.java create mode 100644 src/test/java/kr/co/morandi/backend/factory/TestProblemFactory.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatusTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonResultTypeTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookieTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookieTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookieTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResultTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/JudgementResultTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmitTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCodeTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/SubmitVisibilityTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParserTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCodeTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitStrategyTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitControllerTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieControllerTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepositoryTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepositoryTest.java create mode 100644 src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepositoryTest.java diff --git a/build.gradle b/build.gradle index 41e8057c..59f169d8 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,12 @@ dependencies { // validation implementation 'org.springframework.boot:spring-boot-starter-validation' + // Jsoup + implementation 'org.jsoup:jsoup:1.16.1' + + // Pusher java client + implementation group: 'com.pusher', name: 'pusher-java-client', version: '2.4.4' + // WebFlux (WebClient) implementation 'org.springframework.boot:spring-boot-starter-webflux' diff --git a/src/docs/asciidoc/api/cookie/cookie.adoc b/src/docs/asciidoc/api/cookie/cookie.adoc new file mode 100644 index 00000000..a0952464 --- /dev/null +++ b/src/docs/asciidoc/api/cookie/cookie.adoc @@ -0,0 +1,11 @@ +[[Baekjoon-Cookie]] +== 백준 쿠키 저장하기 + +=== Request +include::{snippets}/save-member-baekjoon-cookie/http-request.adoc[] +include::{snippets}/save-member-baekjoon-cookie/request-fields.adoc[] + +=== Response +include::{snippets}/save-member-baekjoon-cookie/http-response.adoc[] + + diff --git a/src/docs/asciidoc/api/submit/submit.adoc b/src/docs/asciidoc/api/submit/submit.adoc new file mode 100644 index 00000000..aa5c1c5a --- /dev/null +++ b/src/docs/asciidoc/api/submit/submit.adoc @@ -0,0 +1,10 @@ +[[Submit-for-Judgement]] +== 제출 후 채점 요청 + +=== Request +include::{snippets}/submit-for-judgement/http-request.adoc[] +include::{snippets}/submit-for-judgement/request-fields.adoc[] + +=== Response +include::{snippets}/submit-for-judgement/http-response.adoc[] + diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 7f9a8885..cb2128a7 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -13,5 +13,9 @@ include::api/defensesession/defensesession.adoc[] include::api/dailydefense/dailydefense.adoc[] +include::api/cookie/cookie.adoc[] + +include::api/submit/submit.adoc[] + [[Daily-Defense-List]] \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/common/annotation/Adapter.java b/src/main/java/kr/co/morandi/backend/common/annotation/Adapter.java new file mode 100644 index 00000000..6ceeb956 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/annotation/Adapter.java @@ -0,0 +1,17 @@ +package kr.co.morandi.backend.common.annotation; + + +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface Adapter { + @AliasFor(annotation = Component.class) + String value() default ""; + +} diff --git a/src/main/java/kr/co/morandi/backend/common/annotation/Usecase.java b/src/main/java/kr/co/morandi/backend/common/annotation/Usecase.java new file mode 100644 index 00000000..05fd11c7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/annotation/Usecase.java @@ -0,0 +1,17 @@ +package kr.co.morandi.backend.common.annotation; + + +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface Usecase { + @AliasFor(annotation = Component.class) + String value() default ""; + +} diff --git a/src/main/java/kr/co/morandi/backend/common/config/AsyncConfig.java b/src/main/java/kr/co/morandi/backend/common/config/AsyncConfig.java new file mode 100644 index 00000000..873a6ec2 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/config/AsyncConfig.java @@ -0,0 +1,48 @@ +package kr.co.morandi.backend.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@EnableAsync +@Configuration +public class AsyncConfig { + + // 백준 채점 API 요청을 비동기로 처리하기 위한 Executor + @Bean(name = "submitBaekjoonApiExecutor") + public ThreadPoolTaskExecutor baekjoonJudgementThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); // 기본 스레드 수 + executor.setMaxPoolSize(10); // 최대 스레드 수 + executor.setQueueCapacity(25); // 대기열 크기 + executor.setThreadNamePrefix("baekjoon-judgement-"); + executor.initialize(); + return executor; + } + + // 임시 코드 저장을 비동기로 처리하기 위한 Executor + @Bean(name = "tempCodeSaveExecutor") + public ThreadPoolTaskExecutor tempCodeSaveThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); // 기본 스레드 수 + executor.setMaxPoolSize(10); // 최대 스레드 수 + executor.setQueueCapacity(25); // 대기열 크기 + executor.setThreadNamePrefix("temp-code-save-"); + executor.initialize(); + return executor; + } + + // 채점 결과 업데이트를 비동기로 처리하기 위한 Executor + @Bean(name = "updateJudgementStatusExecutor") + public ThreadPoolTaskExecutor updateJudgementStatusThreadPool() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); // 기본 스레드 수 + executor.setMaxPoolSize(10); // 최대 스레드 수 + executor.setQueueCapacity(25); // 대기열 크기 + executor.setThreadNamePrefix("update-judgement-status-"); + executor.initialize(); + return executor; + } + +} diff --git a/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java b/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java index dec59955..980d1a78 100644 --- a/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/kr/co/morandi/backend/common/exception/handler/GlobalExceptionHandler.java @@ -4,16 +4,22 @@ import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; import kr.co.morandi.backend.common.exception.response.ErrorResponse; +import kr.co.morandi.backend.common.web.response.ApiUtils; import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import java.util.stream.Collectors; + import static kr.co.morandi.backend.common.exception.handler.exception.CommonErrorCode.INTERNAL_SERVER_ERROR; import static kr.co.morandi.backend.member_management.infrastructure.config.jwt.constants.TokenType.REFRESH_TOKEN; @@ -39,6 +45,18 @@ public ResponseEntity morandiExceptionHandler(MorandiException e) return handleException(e.getErrorCode()); } + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + String errorsString = ex.getBindingResult() + .getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining(", ")); + + ApiUtils.ApiResult errorResponse = ApiUtils.error(errorsString); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + /** * 예상 외의 에러가 발생한 경우 */ diff --git a/src/main/java/kr/co/morandi/backend/common/model/BaseEntity.java b/src/main/java/kr/co/morandi/backend/common/model/BaseEntity.java index dcf52ab7..38c38397 100644 --- a/src/main/java/kr/co/morandi/backend/common/model/BaseEntity.java +++ b/src/main/java/kr/co/morandi/backend/common/model/BaseEntity.java @@ -5,7 +5,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -14,7 +13,6 @@ @MappedSuperclass @Getter -@SuperBuilder @EntityListeners(AuditingEntityListener.class) @NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class BaseEntity { diff --git a/src/main/java/kr/co/morandi/backend/common/web/response/ApiUtils.java b/src/main/java/kr/co/morandi/backend/common/web/response/ApiUtils.java new file mode 100644 index 00000000..2cc93d84 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/web/response/ApiUtils.java @@ -0,0 +1,51 @@ +package kr.co.morandi.backend.common.web.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ApiUtils { + + public static ApiResult success(T response) { + return new ApiResult<>(true, response); + } + + public static ApiResult success() { + return new ApiResult<>(true, null); + } + + public static ApiResult error(Throwable throwable) { + return new ApiResult<>(false, new ApiError(throwable)); + } + + public static ApiResult error(String message) { + return new ApiResult<>(false, new ApiError(message)); + } + + @Getter + public static class ApiError { + private final String message; + + ApiError(Throwable throwable) { + this(throwable.getMessage()); + } + + ApiError(String message) { + this.message = message; + } + + } + + @Getter + public static class ApiResult { + private final boolean success; + private final T response; + + private ApiResult(boolean success, T response) { + this.success = success; + this.response = response; + } + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefense.java index b2880170..51d0ed85 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/customdefense/CustomDefense.java @@ -9,7 +9,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; import java.util.ArrayList; @@ -21,7 +20,6 @@ @Entity @DiscriminatorValue("CustomDefense") @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) public class CustomDefense extends Defense { @@ -42,7 +40,6 @@ public class CustomDefense extends Defense { @ManyToOne(fetch = FetchType.LAZY) private Member member; - @Builder.Default @OneToMany(mappedBy = "customDefense", cascade = CascadeType.ALL) private List customDefenseProblems = new ArrayList<>(); @@ -51,7 +48,9 @@ public LocalDateTime getEndTime(LocalDateTime startTime) { return startTime.plusMinutes(timeLimit); } - public static CustomDefense create(List problems, Member member, String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { + public static CustomDefense create(List problems, Member member, String contentName, + String description, Visibility visibility, DefenseTier defenseTier, + Long timeLimit, LocalDateTime createDate) { return new CustomDefense(problems, member, contentName, description, visibility, defenseTier, timeLimit, createDate); } @@ -69,6 +68,7 @@ private int isValidProblemCount(int problemCount) { return problemCount; } + @Builder private CustomDefense(List problems, Member member, String contentName, String description, Visibility visibility, DefenseTier defenseTier, Long timeLimit, LocalDateTime createDate) { super(contentName, CUSTOM); diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefense.java index 99436c59..a8483467 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDefense.java @@ -11,7 +11,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.time.LocalDate; import java.time.LocalDateTime; @@ -25,7 +24,6 @@ @Entity @DiscriminatorValue("DailyDefense") @Getter -@SuperBuilder @AllArgsConstructor @NoArgsConstructor public class DailyDefense extends Defense { @@ -34,7 +32,6 @@ public class DailyDefense extends Defense { private Integer problemCount; - @Builder.Default @OneToMany(mappedBy = "dailyDefense", cascade = CascadeType.ALL) List dailyDefenseProblems = new ArrayList<>(); @@ -53,6 +50,7 @@ public Map getTryingProblem(Long problemNumber, ProblemGeneration return tryProblem; } + @Builder private DailyDefense(LocalDate date, String contentName, Map problems) { super(contentName, DAILY); this.date = date; diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java index 7e2ee6db..0da79f38 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/defense/Defense.java @@ -7,7 +7,6 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; import java.util.Map; @@ -16,7 +15,6 @@ @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class Defense extends BaseEntity { diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/model/RandomDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/model/RandomDefense.java index 336f4844..9cd8ce9f 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/model/RandomDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/model/RandomDefense.java @@ -1,10 +1,14 @@ package kr.co.morandi.backend.defense_information.domain.model.randomdefense.model; -import jakarta.persistence.*; -import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; -import lombok.*; -import lombok.experimental.SuperBuilder; +import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; @@ -13,7 +17,6 @@ @Entity @DiscriminatorValue("RandomDefense") @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) public class RandomDefense extends Defense { @@ -29,6 +32,7 @@ public LocalDateTime getEndTime(LocalDateTime startTime) { return startTime.plusMinutes(timeLimit); } + @Builder private RandomDefense(RandomCriteria randomCriteria, Integer problemCount, Long timeLimit, String contentName) { super(contentName, RANDOM); this.randomCriteria = randomCriteria; diff --git a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/model/StageDefense.java b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/model/StageDefense.java index 9a58bc4f..7f2f508c 100644 --- a/src/main/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/model/StageDefense.java +++ b/src/main/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/model/StageDefense.java @@ -1,10 +1,14 @@ package kr.co.morandi.backend.defense_information.domain.model.stagedefense.model; -import jakarta.persistence.*; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import kr.co.morandi.backend.defense_information.domain.model.defense.RandomCriteria; -import lombok.*; -import lombok.experimental.SuperBuilder; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; @@ -13,7 +17,6 @@ @Entity @DiscriminatorValue("StageDefense") @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) public class StageDefense extends Defense { @@ -40,6 +43,7 @@ private Long isValidTimeLimit(Long timeLimit) { return timeLimit; } + @Builder private StageDefense(RandomCriteria randomCriteria, Long timeLimit, String contentName) { super(contentName, STAGE); this.randomCriteria = randomCriteria; diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java index 4a04b0e8..65a1f401 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/session/DefenseSessionPort.java @@ -11,4 +11,5 @@ public interface DefenseSessionPort { DefenseSession saveDefenseSession(DefenseSession defenseSession); Optional findTodaysDailyDefenseSession(Member member, LocalDateTime now); Optional findDefenseSessionById(Long sessionId); + Optional findDefenseSessionJoinFetchTempCode(Long sessionId); } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/error/LanguageErrorCode.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/error/LanguageErrorCode.java new file mode 100644 index 00000000..deaa93a0 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/error/LanguageErrorCode.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.defense_management.domain.error; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum LanguageErrorCode implements ErrorCode { + + LANGUAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "존재하지 않는 언어 값입니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java index 53c491e2..93836589 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java @@ -5,7 +5,7 @@ import kr.co.morandi.backend.common.model.BaseEntity; import kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType; import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; -import kr.co.morandi.backend.defense_management.domain.service.SessionService; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; import kr.co.morandi.backend.member_management.domain.model.member.Member; import lombok.AccessLevel; import lombok.Builder; @@ -57,6 +57,14 @@ public void validateSessionOwner(Long memberId) { } } + public void updateTempCode(Long problemNumber, Language language, String code) { + if(isTerminated()) { + throw new MorandiException(SessionErrorCode.SESSION_ALREADY_ENDED); + } + SessionDetail sessionDetail = getSessionDetail(problemNumber); + sessionDetail.updateTempCode(language, code); + } + public boolean isTerminated() { return examStatus == ExamStatus.COMPLETED; } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java index 8c339c7e..7117a8be 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/SessionDetail.java @@ -34,6 +34,10 @@ public class SessionDetail extends BaseEntity { public static SessionDetail create(DefenseSession defenseSession, Long problemNumber) { return new SessionDetail(defenseSession, problemNumber); } + /* + * getTempCode는 만약 없는 언어로 tempCode를 get해도 + * addTempCode를 호출해서 추가하고, 예외를 반환하지 않는다. + * */ public TempCode getTempCode(Language language) { Optional maybeTempCode = getTempCodes().stream() .filter(tempcode -> tempcode.getLanguage().equals(language)) @@ -42,42 +46,25 @@ public TempCode getTempCode(Language language) { return maybeTempCode.orElseGet(() -> addTempCode(language, language.getInitialCode())); } - public TempCode addTempCode(Language language, String code) { - TempCode tempCode = TempCode.create(language, code, this); - getTempCodes().add(tempCode); + public void updateTempCode(Language language, String code) { + this.lastAccessLanguage = language; - return tempCode; + TempCode tempCode = getTempCode(language); + tempCode.updateTempCode(code); } - /* - * 만약 없는 언어로 tempCode를 update하려고 했더라도 - * addTempCode를 호출해서 추가하고, 예외를 반환하지 않는다. - * */ - public void updateTempCode(Language language, String code) { - this.lastAccessLanguage = language; - final Optional tempCode = getTempCodes().stream() - .filter(tempcode -> tempcode.getLanguage().equals(language)) - .findFirst(); + protected TempCode addTempCode(Language language, String code) { + TempCode tempCode = TempCode.create(language, code, this); + getTempCodes().add(tempCode); - if(tempCode.isPresent()) { - tempCode.get().updateTempCode(code); - return; - } - // 만약 없는 언어로 tempCode를 update하려고 했으면 addTempCode를 호출해서 추가해준다. - addTempCode(language, code); + return tempCode; } + @Builder private SessionDetail(DefenseSession defenseSession, Long problemNumber) { this.defenseSession = defenseSession; this.problemNumber = problemNumber; this.lastAccessLanguage = INITIAL_LANGUAGE; this.tempCodes.add(TempCode.create(INITIAL_LANGUAGE, INITIAL_LANGUAGE.getInitialCode(), this)); } - @Builder - private SessionDetail(DefenseSession defenseSession, Set tempCodes, Long problemNumber, Language lastAccessLanguage) { - this.defenseSession = defenseSession; - this.tempCodes = tempCodes; - this.problemNumber = problemNumber; - this.lastAccessLanguage = lastAccessLanguage; - } } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/Language.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/Language.java index afbc970b..b7b5f149 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/Language.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/Language.java @@ -1,19 +1,23 @@ package kr.co.morandi.backend.defense_management.domain.model.tempcode.model; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.domain.error.LanguageErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public enum Language { - JAVA(""" + JAVA("JAVA", """ public class Main { public static void main(String[] args) { System.out.println("Hello World"); } } """), - CPP(""" + CPP("CPP",""" #include using namespace std; @@ -22,9 +26,24 @@ int main() { return 0; } """), - PYTHON(""" + PYTHON("PYTHON",""" print("Hello World") """); + private final String value; private final String initialCode; + + @JsonValue + public String getValue() { + return value; + } + @JsonCreator + public static Language from(String value) { + for (Language language : values()) { + if(language.getValue().equals(value)) { + return language; + } + } + throw new MorandiException(LanguageErrorCode.LANGUAGE_NOT_FOUND); + } } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/TempCode.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/TempCode.java index 0adacd11..d9c8c6ee 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/TempCode.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/TempCode.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import kr.co.morandi.backend.common.model.BaseEntity; import kr.co.morandi.backend.defense_management.domain.model.session.SessionDetail; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -19,11 +20,20 @@ public class TempCode extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private SessionDetail sessionDetail; - @Enumerated(EnumType.STRING) - private Language language; + @Embedded + private SourceCode sourceCode; - @Column(columnDefinition = "TEXT") - private String code; + public Language getLanguage() { + return sourceCode.getLanguage(); + } + + public String getCode() { + return sourceCode.getSourceCode(); + } + + public void updateTempCode(String code) { + sourceCode.updateSourceCode(code); + } public static TempCode create(Language language, String code, SessionDetail sessionDetail) { return TempCode.builder() @@ -33,15 +43,10 @@ public static TempCode create(Language language, String code, SessionDetail sess .build(); } - public void updateTempCode(String code) { - this.code = code; - } - @Builder private TempCode(SessionDetail sessionDetail, Language language, String code) { this.sessionDetail = sessionDetail; - this.language = language; - this.code = code; + this.sourceCode = SourceCode.of(code, language); } @Override diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java index ce382763..36196158 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/DefenseEventService.java @@ -13,7 +13,7 @@ public class DefenseEventService { private final DefenseTimerService defenseTimerService; - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) public void onDefenseStartTimerEvent(DefenseStartTimerEvent event) { defenseTimerService.startDefenseTimer(event.getSessionId(), event.getStartDateTime(), event.getEndDateTime()); } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java index 51fbbfde..5d23f996 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/SessionService.java @@ -6,6 +6,7 @@ import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; import kr.co.morandi.backend.defense_record.application.port.out.record.RecordPort; import kr.co.morandi.backend.defense_record.domain.error.RecordErrorCode; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -25,12 +26,12 @@ public void terminateDefense(Long sessionId) { defenseSession.terminateSession(); - final Record record = recordPort.findRecordById(defenseSession.getRecordId()) + final Record foundRecord = recordPort.findRecordById(defenseSession.getRecordId()) .orElseThrow(() -> new MorandiException(RecordErrorCode.RECORD_NOT_FOUND)); - record.terminteDefense(); + foundRecord.terminteDefense(); defenseSessionPort.saveDefenseSession(defenseSession); - recordPort.saveRecord(record); + recordPort.saveRecord(foundRecord); } } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveService.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveService.java new file mode 100644 index 00000000..feabee60 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveService.java @@ -0,0 +1,39 @@ +package kr.co.morandi.backend.defense_management.domain.service; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.judgement.domain.event.TempCodeSaveEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Slf4j +@RequiredArgsConstructor +public class TempCodeSaveService { + + private final DefenseSessionPort defenseSessionPort; + + @EventListener + @Transactional + @Async("tempCodeSaveExecutor") + public void saveTempCode(final TempCodeSaveEvent tempCodeSaveEvent) { + log.info("Save Temp Code Event: defenseSessionId: {}, problemNumber: {}, language: {}", + tempCodeSaveEvent.defenseSessionId(), tempCodeSaveEvent.problemNumber(), tempCodeSaveEvent.language()); + final Long defenseSessionId = tempCodeSaveEvent.defenseSessionId(); + + final DefenseSession defenseSession = defenseSessionPort.findDefenseSessionJoinFetchTempCode(defenseSessionId) + .orElseThrow(() -> new MorandiException(SessionErrorCode.SESSION_NOT_FOUND)); + + defenseSession.updateTempCode(tempCodeSaveEvent.problemNumber(), + tempCodeSaveEvent.language(), + tempCodeSaveEvent.sourceCode()); + + defenseSessionPort.saveDefenseSession(defenseSession); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java index 37a2af7b..ae0dd86f 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/session/DefenseSessionAdapter.java @@ -32,4 +32,9 @@ public Optional findTodaysDailyDefenseSession(Member member, Loc public Optional findDefenseSessionById(Long sessionId) { return defenseSessionRepository.findById(sessionId); } + + @Override + public Optional findDefenseSessionJoinFetchTempCode(Long sessionId) { + return defenseSessionRepository.findDefenseSessionJoinFetchTempCode(sessionId); + } } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepository.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepository.java index 4792e571..8de66e0e 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepository.java @@ -19,4 +19,13 @@ public interface DefenseSessionRepository extends JpaRepository findDailyDefenseSession(Member member, DefenseType defenseType, LocalDateTime now); + + @Query(""" + select ds + from DefenseSession as ds + left join fetch ds.sessionDetails d + left join fetch d.tempCodes + where ds.defenseSessionId = :sessionId + """) + Optional findDefenseSessionJoinFetchTempCode(Long sessionId); } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java index ab893949..a5f1963d 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/application/port/out/record/RecordPort.java @@ -1,10 +1,13 @@ package kr.co.morandi.backend.defense_record.application.port.out.record; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_record.domain.model.record.Record; import java.util.Optional; public interface RecordPort { - Optional> findRecordById(Long recordId); - void saveRecord(Record record); + Optional> findRecordById(Long recordId); + Optional> findRecordFetchJoinWithDetail(Long recordId); + Optional> findRecordFetchJoinWithDetailAndProblem(Long recordId); + void saveRecord(Record record); } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java index da8bfd95..03ac35cc 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/error/RecordErrorCode.java @@ -9,8 +9,8 @@ @RequiredArgsConstructor public enum RecordErrorCode implements ErrorCode { RECORD_NOT_FOUND(HttpStatus.NOT_FOUND, "시험 기록(Record)를 찾을 수 없습니다."), - RECORD_ALREADY_TERMINATED(HttpStatus.BAD_REQUEST, "이미 종료된 시험 기록입니다.") - ; + RECORD_ALREADY_TERMINATED(HttpStatus.BAD_REQUEST, "이미 종료된 시험 기록입니다."), + DETAIL_NOT_FOUND(HttpStatus.BAD_REQUEST, "해당 번호의 문제 풀이 기록을 찾을 수 없습니다."),; @Override public HttpStatus getHttpStatus() { return httpStatus; diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomDetail.java index ed123f9a..29ab9f8b 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomDetail.java @@ -2,18 +2,17 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; @Entity -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @DiscriminatorValue("CustomDefenseProblemRecord") @@ -22,13 +21,20 @@ public class CustomDetail extends Detail { private Long problemNumber; private Long solvedTime; + @Override + public Long getSequenceNumber() { + return problemNumber; + } + private static final long INITIAL_SOLVED_TIME = 0L; - private CustomDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + + @Builder + private CustomDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { super(member, problem, records, defense); this.problemNumber = sequenceNumber; this.solvedTime = INITIAL_SOLVED_TIME; } - public static CustomDetail create(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + public static CustomDetail create(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { return new CustomDetail(member, sequenceNumber, problem, records, defense); } } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java index f55418b6..826815b5 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecord.java @@ -2,35 +2,34 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import kr.co.morandi.backend.defense_information.domain.model.customdefense.CustomDefense; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; import java.util.Map; @Entity -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @DiscriminatorValue("CustomRecord") public class CustomRecord extends Record { - private Integer solvedCount; private Integer problemCount; @Override public CustomDetail createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { return CustomDetail.create(member, sequenceNumber, problem, records, defense); } + + @Builder private CustomRecord(CustomDefense customDefense, Member member, LocalDateTime testDate, Map problems) { super(testDate, customDefense, member, problems); - this.solvedCount = 0; this.problemCount = customDefense.getProblemCount(); } public static CustomRecord create(CustomDefense customDefense, Member member, LocalDateTime testDate, Map problems) { diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java index 4df3bb6a..7d381913 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyDetail.java @@ -2,31 +2,35 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; @Entity -@SuperBuilder @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("DailyDefenseProblemRecord") public class DailyDetail extends Detail { - Long problemNumber; + private Long problemNumber; + @Override + public Long getSequenceNumber() { + return problemNumber; + } - private DailyDetail(Member member, Long problemNumber, Problem problem, Record records, Defense defense) { + @Builder + private DailyDetail(Member member, Long problemNumber, Problem problem, Record records, Defense defense) { super(member, problem, records, defense); this.problemNumber = problemNumber; } - public static DailyDetail create(Member member, Long problemNumber, Problem problem, Record records, Defense defense) { + public static DailyDetail create(Member member, Long problemNumber, Problem problem, Record records, Defense defense) { return new DailyDetail(member, problemNumber, problem, records, defense); } } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java index ef73c7cd..4f84cd16 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecord.java @@ -2,18 +2,17 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; -import java.time.Duration; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -22,28 +21,12 @@ @Entity @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("DailyDefenseRecord") public class DailyRecord extends Record { - private Long solvedCount; - private Integer problemCount; - - public void solveProblem(Long problemNumber, String code, LocalDateTime solvedAt) { - super.getDetails().stream() - .filter(detail -> detail.getProblemNumber().equals(problemNumber)) - .findFirst() - .ifPresent(detail -> { - Long solvedTime = calculateSolvedTime(solvedAt); - - if(detail.solveProblem(code, solvedTime)) { - ++this.solvedCount; - super.addTotalSolvedTime(solvedTime); - } - }); - } + private Integer problemCount; public Set getSolvedProblemNumbers() { return super.getDetails().stream() @@ -89,14 +72,12 @@ public void tryMoreProblem(Map problem) { // 새로운 문제 추가로 문제 수 증가 this.problemCount += newDetails.size(); } + + @Builder private DailyRecord(LocalDateTime date, Defense defense, Member member, Map problems) { super(date, defense, member, problems); - this.solvedCount = 0L; this.problemCount = problems.size(); defense.increaseAttemptCount(); } - private long calculateSolvedTime(LocalDateTime solvedAt) { - return Duration.between(super.getTestDate(), solvedAt).getSeconds(); - } } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomDetail.java index 57f2c6d5..53f1b3c8 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomDetail.java @@ -2,18 +2,17 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; @Entity -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @DiscriminatorValue("RandomDefenseProblemRecord") @@ -22,14 +21,21 @@ public class RandomDetail extends Detail { private Long problemNumber; private Long solvedTime; + @Override + public Long getSequenceNumber() { + return problemNumber; + } + private static final long INITIAL_SOLVED_TIME = 0L; - private RandomDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + + @Builder + private RandomDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { super(member, problem, records, defense); this.problemNumber = sequenceNumber; this.solvedTime = INITIAL_SOLVED_TIME; } - public static RandomDetail create(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { + public static RandomDetail create(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense) { return new RandomDetail(member, sequenceNumber, problem, records, defense); } } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecord.java index 363033b9..19e59fe4 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecord.java @@ -4,32 +4,28 @@ import jakarta.persistence.Entity; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; import kr.co.morandi.backend.defense_information.domain.model.randomdefense.model.RandomDefense; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; import java.util.Map; @Entity @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("RandomDefenseRecord") public class RandomRecord extends Record { - private Integer solvedCount; private Integer problemCount; - private static final Integer INITIAL_SOLVED_COUNT = 0; - + @Builder private RandomRecord(LocalDateTime testDate, RandomDefense randomDefense, Member member, Map problems) { super(testDate, randomDefense, member, problems); - this.solvedCount = INITIAL_SOLVED_COUNT; this.problemCount = problems.size(); } @Override diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java index 9beaf3fc..1bbac35c 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Detail.java @@ -8,13 +8,14 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; + +import java.time.Duration; +import java.time.LocalDateTime; @Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class Detail extends BaseEntity { @@ -25,13 +26,11 @@ public abstract class Detail extends BaseEntity { private Long submitCount; - private String solvedCode; - @ManyToOne(fetch = FetchType.LAZY) private Defense defense; @ManyToOne(fetch = FetchType.LAZY) - private Record record; + private Record record; @ManyToOne(fetch = FetchType.LAZY) private Member member; @@ -39,29 +38,47 @@ public abstract class Detail extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private Problem problem; + private Long correctSubmitId; + private Long solvedTime; + public abstract Long getSequenceNumber(); + private static final Long INITIAL_SUBMIT_COUNT = 0L; private static final Long INITIAL_SOLVED_TIME = 0L; private static final Boolean INITIAL_IS_SOLVED = false; - public boolean solveProblem(String solvedCode, Long solvedTime) { - if(this.isSolved) { - return false; + public void trySolveProblem(Long submitId, LocalDateTime solvedDateTime) { + if(isSolvedDetail()) { + return ; } this.isSolved = true; - this.solvedCode = solvedCode; - this.solvedTime = solvedTime; - return true; + this.correctSubmitId = submitId; + this.solvedTime = calculateSolvedTime(solvedDateTime); + record.addSolvedCountAndTime(this.solvedTime); + } + public void increaseSubmitCount() { + this.submitCount++; } - protected Detail(Member member, Problem problem, Record records, Defense defense) { + + private boolean isSolvedDetail() { + return Boolean.TRUE.equals(this.isSolved); + } + + private long calculateSolvedTime(LocalDateTime nowDateTime) { + LocalDateTime startTime = this.record.getTestDate(); + return Duration.between(startTime, nowDateTime).toSeconds(); + } + + protected Detail(Member member, Problem problem, Record records, Defense defense) { this.isSolved = INITIAL_IS_SOLVED; this.submitCount = INITIAL_SUBMIT_COUNT; this.solvedTime = INITIAL_SOLVED_TIME; - this.solvedCode = null; + this.correctSubmitId = null; this.defense = defense; this.record = records; this.member = member; this.problem = problem; } + } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java index 252676f7..cf6cec99 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/Record.java @@ -8,10 +8,8 @@ import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; import java.util.ArrayList; @@ -23,7 +21,6 @@ @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn @Getter -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class Record extends BaseEntity { @@ -38,7 +35,6 @@ public abstract class Record extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private Member member; - @Builder.Default @OneToMany(mappedBy = "record", cascade = CascadeType.ALL, targetEntity = Detail.class) private List details = new ArrayList<>(); @@ -47,7 +43,24 @@ public abstract class Record extends BaseEntity { @Enumerated(EnumType.STRING) private RecordStatus status; + private Long totalSolvedCount; + private static final Long INITIAL_TOTAL_SOLVED_TIME = 0L; + private static final Long INITIAL_TOTAL_SOLVED_COUNT = 0L; + + public T getDetail(Long sequenceNumber) { + return this.details.stream() + .filter(detail -> detail.getSequenceNumber().equals(sequenceNumber)) + .findFirst() + .orElseThrow(() -> new MorandiException(RecordErrorCode.DETAIL_NOT_FOUND)); + } + + public Problem getProblem(Long sequenceNumber) { + return getDetail(sequenceNumber).getProblem(); + } + public boolean isTerminated() { + return this.status.equals(RecordStatus.COMPLETED); + } public boolean terminteDefense() { if(this.status.equals(RecordStatus.COMPLETED)) { @@ -56,14 +69,16 @@ public boolean terminteDefense() { this.status = RecordStatus.COMPLETED; return true; } - public void addTotalSolvedTime(Long totalSolvedTime) { + public void addSolvedCountAndTime(Long totalSolvedTime) { this.totalSolvedTime += totalSolvedTime; + this.totalSolvedCount += 1; } protected abstract T createDetail(Member member, Long sequenceNumber, Problem problem, Record records, Defense defense); protected Record(LocalDateTime testDate, Defense defense, Member member, Map problems) { this.testDate = testDate; this.defense = defense; + this.totalSolvedCount = INITIAL_TOTAL_SOLVED_COUNT; this.member = member; this.status = RecordStatus.IN_PROGRESS; this.totalSolvedTime = INITIAL_TOTAL_SOLVED_TIME; diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/SubmitCode.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/SubmitCode.java deleted file mode 100644 index eb09b084..00000000 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/record/SubmitCode.java +++ /dev/null @@ -1,32 +0,0 @@ -package kr.co.morandi.backend.defense_record.domain.model.record; - -import jakarta.persistence.*; -import kr.co.morandi.backend.member_management.domain.model.member.Member; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class SubmitCode { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long submitRecordId; - - private String submitCodeLink; - - @ManyToOne(fetch = FetchType.LAZY) - private Member member; - - @ManyToOne(fetch = FetchType.LAZY) - private Detail detail; - - @Builder - private SubmitCode(String submitCodeLink, Member member, Detail detail) { - this.submitCodeLink = submitCodeLink; - this.member = member; - this.detail = detail; - } -} diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java index 58d9ff9b..b9ca3a06 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageDetail.java @@ -2,18 +2,17 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; @Entity -@SuperBuilder @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @DiscriminatorValue("StageDefenseProblemRecord") @@ -21,11 +20,17 @@ public class StageDetail extends Detail { private Long stageNumber; - private StageDetail(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { + @Override + public Long getSequenceNumber() { + return stageNumber; + } + + @Builder + private StageDetail(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { super(member, problem, records, defense); this.stageNumber = stageNumber; } - public static StageDetail create(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { + public static StageDetail create(Member member, Long stageNumber, Problem problem, Record records, Defense defense) { return new StageDetail(member, stageNumber, problem, records, defense); } } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java index fb9eb513..66436211 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecord.java @@ -3,19 +3,18 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import kr.co.morandi.backend.defense_information.domain.model.defense.Defense; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import kr.co.morandi.backend.defense_record.domain.model.record.Record; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; import java.util.Map; @Entity -@SuperBuilder @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("StageDefenseRecord") @@ -26,6 +25,7 @@ public class StageRecord extends Record { private static final Long INITIAL_STAGE_NUMBER = 1L; private static final Long INITIAL_STAGE_COUNT = 1L; + @Builder private StageRecord(Defense defense, LocalDateTime testDate, Member member, Map problems) { super(testDate, defense, member, problems); this.stageCount = INITIAL_STAGE_COUNT; diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java index fd48f8b3..d45794e0 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapter.java @@ -1,25 +1,36 @@ package kr.co.morandi.backend.defense_record.infrastructure.adapter.record; +import kr.co.morandi.backend.common.annotation.Adapter; import kr.co.morandi.backend.defense_record.application.port.out.record.RecordPort; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.defense_record.infrastructure.persistence.record.RecordRepository; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; import java.util.Optional; -@Component +@Adapter @RequiredArgsConstructor public class RecordAdapter implements RecordPort { private final RecordRepository recordRepository; @Override - public Optional> findRecordById(Long recordId) { - return recordRepository.findById(recordId); + public Optional> findRecordById(Long defenseRecordId) { + return recordRepository.findById(defenseRecordId); } @Override - public void saveRecord(Record record) { - recordRepository.save(record); + public Optional> findRecordFetchJoinWithDetail(Long defenseRecordId) { + return recordRepository.findRecordFetchJoinWithDetail(defenseRecordId); + } + + @Override + public Optional> findRecordFetchJoinWithDetailAndProblem(Long defenseRecordId) { + return recordRepository.findRecordFetchJoinWithDetailAndProblem(defenseRecordId); + } + + @Override + public void saveRecord(Record defenseRecord) { + recordRepository.save(defenseRecord); } } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java index 2bb3c0af..fc82264b 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepository.java @@ -39,7 +39,7 @@ and CAST(dr.testDate as localdate) = :date select dr from DailyRecord dr where CAST(dr.testDate as localdate) = :requestDate - order by dr.solvedCount desc, dr.totalSolvedTime asc, dr.recordId asc + order by dr.totalSolvedCount desc, dr.totalSolvedTime asc, dr.recordId asc """) Page getDailyRecordsRankByDate(LocalDate requestDate, Pageable pageable); } diff --git a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java index 7fc61c7f..2bc07da0 100644 --- a/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java +++ b/src/main/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/record/RecordRepository.java @@ -1,7 +1,28 @@ package kr.co.morandi.backend.defense_record.infrastructure.persistence.record; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_record.domain.model.record.Record; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; -public interface RecordRepository extends JpaRepository, Long> { +import java.util.Optional; + +public interface RecordRepository extends JpaRepository, Long> { + + @Query(""" + SELECT r + FROM Record r + LEFT JOIN FETCH r.details d + WHERE r.recordId = :recordId + """) + Optional> findRecordFetchJoinWithDetail(Long recordId); + + @Query(""" + SELECT r + FROM Record r + LEFT JOIN FETCH r.details d + LEFT JOIN FETCH d.problem + WHERE r.recordId = :recordId + """) + Optional> findRecordFetchJoinWithDetailAndProblem(Long recordId); } diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/port/out/BaekjoonSubmitPort.java b/src/main/java/kr/co/morandi/backend/judgement/application/port/out/BaekjoonSubmitPort.java new file mode 100644 index 00000000..2100b11e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/port/out/BaekjoonSubmitPort.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.judgement.application.port.out; + +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; + +import java.util.Optional; + +public interface BaekjoonSubmitPort { + BaekjoonSubmit save(BaekjoonSubmit submit); + + Optional findSubmitJoinFetchDetailAndRecord(Long submitId); +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/request/JudgementServiceRequest.java b/src/main/java/kr/co/morandi/backend/judgement/application/request/JudgementServiceRequest.java new file mode 100644 index 00000000..bad20948 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/request/JudgementServiceRequest.java @@ -0,0 +1,35 @@ +package kr.co.morandi.backend.judgement.application.request; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JudgementServiceRequest { + + private Long defenseSessionId; + private Long memberId; + private Long problemNumber; + private Language language; + private String sourceCode; + private SubmitVisibility submitVisibility; + private LocalDateTime nowDateTime; + + @Builder + private JudgementServiceRequest(Long defenseSessionId, Long memberId, Long problemNumber, Language language, String sourceCode, SubmitVisibility submitVisibility, LocalDateTime nowDateTime) { + this.defenseSessionId = defenseSessionId; + this.memberId = memberId; + this.problemNumber = problemNumber; + this.language = language; + this.sourceCode = sourceCode; + this.submitVisibility = submitVisibility; + this.nowDateTime = nowDateTime; + } +} + diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/request/cookie/BaekjoonMemberCookieServiceRequest.java b/src/main/java/kr/co/morandi/backend/judgement/application/request/cookie/BaekjoonMemberCookieServiceRequest.java new file mode 100644 index 00000000..222394f2 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/request/cookie/BaekjoonMemberCookieServiceRequest.java @@ -0,0 +1,10 @@ +package kr.co.morandi.backend.judgement.application.request.cookie; + + +import java.time.LocalDateTime; + +public record BaekjoonMemberCookieServiceRequest( + String cookie, + Long memberId, + LocalDateTime nowDateTime +) {} diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitFacade.java b/src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitFacade.java new file mode 100644 index 00000000..d58280ee --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitFacade.java @@ -0,0 +1,43 @@ +package kr.co.morandi.backend.judgement.application.service; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.application.service.baekjoon.result.JudgementResultSubscriber; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@Slf4j +@RequiredArgsConstructor +public class SubmitFacade { + + private final SubmitStrategy submitStrategy; + private final JudgementResultSubscriber judgementResultSubscriber; + + /* + * 외부 API이기 때문에 트랜잭션 내에서 수행하지 않고 + * 비동기로 처리한다. + * */ + @Async("submitBaekjoonApiExecutor") + public void asyncProcessSubmitAndSubscribeJudgement(final Long submitId, + final Long memberId, + final Problem problem, + final Language language, + final String sourceCode, + final SubmitVisibility submitVisibility, + final LocalDateTime nowDateTime) { + log.info("Submit and Subscribe Judgement submitId: {}, baekjoonProblemId: {}, language: {}, submitVisibility: {}", + submitId, problem.getBaekjoonProblemId(), language, submitVisibility); + final String solutionId = submitStrategy.submit(memberId, language, problem, sourceCode, submitVisibility, nowDateTime); + /* + * solutionId를 바탕으로 websocket을 채널을 등록하는 로직 + * */ + judgementResultSubscriber.subscribeJudgement(solutionId, submitId); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitStrategy.java b/src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitStrategy.java new file mode 100644 index 00000000..646a34ed --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/service/SubmitStrategy.java @@ -0,0 +1,11 @@ +package kr.co.morandi.backend.judgement.application.service; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; + +import java.time.LocalDateTime; + +public interface SubmitStrategy { + String submit(Long memberId, Language language, Problem problem, String sourceCode, SubmitVisibility submitVisibility, LocalDateTime nowDateTime); +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonCookieManager.java b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonCookieManager.java new file mode 100644 index 00000000..47f4d555 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonCookieManager.java @@ -0,0 +1,64 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.cookie; + + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.BaekjoonCookieErrorCode; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonCookie; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonGlobalCookie; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonMemberCookie; +import kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon.BaekjoonGlobalCookieRepository; +import kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon.BaekjoonMemberCookieRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +public class BaekjoonCookieManager { + + private final BaekjoonMemberCookieRepository memberCookieRepository; + private final BaekjoonGlobalCookieRepository globalCookieRepository; + + public String getCurrentMemberCookie(final Long memberId, final LocalDateTime now) { + return memberCookieRepository.findBaekjoonMemberCookieByMember_MemberId(memberId) + .map(cookie -> this.getValidCookieValue(cookie, now)) + .orElseGet(() -> this.getGlobalCookie(now)); + // 사용자 쿠키가 유효하지 않으면 임시방편으로 글로벌 쿠키를 가져와서 채점하도록 유도함 + + // TODO 사용자 쿠키를 재발급하도록 어떻게 알려줄 지 + } + private String getValidCookieValue(BaekjoonMemberCookie memberCookie, LocalDateTime nowDateTime) { + if(memberCookie.isValidCookie(nowDateTime)) { + return memberCookie.getBaekjoonCookie() + .getValue(); + } + log.warn("Member cookie가 유효하지 않습니다. memberId: {}", memberCookie.getMember().getMemberId()); + return getGlobalCookie(nowDateTime); + } + + private String getGlobalCookie(LocalDateTime nowDateTime) { + List validCookies = globalCookieRepository.findValidGlobalCookies(nowDateTime); + if (validCookies.isEmpty()) { + log.warn("Global cookie가 존재하지 않습니다."); + throw new MorandiException(BaekjoonCookieErrorCode.NOT_EXIST_GLOBAL_COOKIE); + } + + Collections.shuffle(validCookies); + // 등록된 쿠키가 여러 개일 때, 여러 쿠키가 균일하게 사용되도록 하기 위해 + + return validCookies.stream() + .filter(cookie -> cookie.isValidCookie(nowDateTime)) + .map(BaekjoonGlobalCookie::getBaekjoonCookie) + .map(BaekjoonCookie::getValue) + .findFirst() + .orElseThrow(() -> { + log.warn("Global cookie가 존재하지 않습니다."); + return new MorandiException(BaekjoonCookieErrorCode.NOT_EXIST_GLOBAL_COOKIE); + }); + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieService.java b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieService.java new file mode 100644 index 00000000..df7fb74c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieService.java @@ -0,0 +1,35 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.cookie; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.application.request.cookie.BaekjoonMemberCookieServiceRequest; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonMemberCookie; +import kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon.BaekjoonMemberCookieRepository; +import kr.co.morandi.backend.member_management.domain.model.error.MemberErrorCode; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class BaekjoonMemberCookieService { + + private final MemberRepository memberRepository; + + @Transactional + public void saveMemberBaekjoonCookie(BaekjoonMemberCookieServiceRequest request) { + final Long memberId = request.memberId(); + final String cookie = request.cookie(); + final LocalDateTime nowDateTime = request.nowDateTime(); + + final Member member = memberRepository.findMemberJoinFetchCookie(memberId) + .orElseThrow(() -> new MorandiException(MemberErrorCode.MEMBER_NOT_FOUND)); + + member.saveBaekjoonCookie(cookie, nowDateTime); + } +} + diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatus.java b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatus.java new file mode 100644 index 00000000..94499a67 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatus.java @@ -0,0 +1,79 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.result; + +import com.fasterxml.jackson.annotation.JsonProperty; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonResultType; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaekjoonJudgementStatus { + + @JsonProperty("result") + private BaekjoonResultType result; + + @JsonProperty("progress") + private Integer progress; + + @JsonProperty("memory") + private Integer memory; + + @JsonProperty("time") + private Integer time; + + @JsonProperty("subtask_score") + private Double subtaskScore; + + @JsonProperty("partial_score") + private Double partialScore; + + @JsonProperty("ac") + private Integer ac; + + @JsonProperty("tot") + private Integer tot; + + @JsonProperty("feedback") + private String feedback; + + @JsonProperty("rte_reason") + private String rteReason; + + @JsonProperty("remain") + private Integer remain; + + public boolean isAccepted() { + return this.getResult().equals(BaekjoonResultType.CORRECT); + } + + public boolean isRejected() { + return this.getResult().equals(BaekjoonResultType.WRONG_ANSWER) || this.getResult().equals(BaekjoonResultType.RUNTIME_ERROR) + || this.getResult().equals(BaekjoonResultType.COMPILE_ERROR) || this.getResult().equals(BaekjoonResultType.TIME_LIMIT_EXCEEDED) + || this.getResult().equals(BaekjoonResultType.OTHER); + } + + public boolean isFinalResult() { + BaekjoonResultType baekjoonResultType = this.getResult(); + return baekjoonResultType.equals(BaekjoonResultType.CORRECT) || baekjoonResultType.equals(BaekjoonResultType.WRONG_ANSWER) + || baekjoonResultType.equals(BaekjoonResultType.RUNTIME_ERROR) || baekjoonResultType.equals(BaekjoonResultType.COMPILE_ERROR) + || baekjoonResultType.equals(BaekjoonResultType.TIME_LIMIT_EXCEEDED) || baekjoonResultType.equals(BaekjoonResultType.OTHER); + } + + @Builder + private BaekjoonJudgementStatus(BaekjoonResultType result, Integer progress, Integer memory, Integer time, + Double subtaskScore, Double partialScore, Integer ac, Integer tot, String feedback, String rteReason, Integer remain) { + this.result = result; + this.progress = progress; + this.memory = memory; + this.time = time; + this.subtaskScore = subtaskScore; + this.partialScore = partialScore; + this.ac = ac; + this.tot = tot; + this.feedback = feedback; + this.rteReason = rteReason; + this.remain = remain; + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/JudgementResultSubscriber.java b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/JudgementResultSubscriber.java new file mode 100644 index 00000000..7c7f621e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/JudgementResultSubscriber.java @@ -0,0 +1,82 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.result; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonJudgementResult; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementStatus; +import kr.co.morandi.backend.judgement.domain.service.BaekjoonJudgementService; +import kr.co.morandi.backend.judgement.infrastructure.baekjoon.result.PusherService; +import kr.co.morandi.backend.judgement.infrastructure.helper.JudgementStatusMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class JudgementResultSubscriber { + + private final PusherService pusherService; + private final ObjectMapper objectMapper; + + // TODO Subscriber가 고수준 Service를 의존하는 것을 해결하고 싶음 + // 1. 인터페이스를 만들어 의존성을 주입하는 방법 + // 2. 이벤트를 이용하는 방법 + // 3. 콜백함수를 이용하는 방법 + // + // Pusher에서 실행하는 콜백함수에서 BaekjoonJudgementService를 사용하게 될 것이고, 저장 행위가 비동기로 이루어져야함 + private final BaekjoonJudgementService baekjoonJudgementService; + private static final String CHANNEL_FORMAT = "solution-%s"; + + public void subscribeJudgement(final String solutionId, final Long submitId) { + /* + * solutionId를 바탕으로 pusher 채널을 구독하는 로직 + * */ + final String solutionChannelId = String.format(CHANNEL_FORMAT, solutionId); + + /* + * 콜백 함수를 파라미터로 전달하여 Listener에서 메세지가 도착하면 콜백함수를 실행하도록 구현 + * */ + pusherService.subscribeJudgement(solutionChannelId, submitId, this::handleResult); + } + + + /* + * 등록된 콜백함수에서 메세지가 도착하면 실행되는 함수 + * + * 결과를 파싱하여 저장하는 로직 + * */ + private void handleResult(final Long submitId, final String data) { + final BaekjoonJudgementStatus baekjoonJudgementStatus = parseJudgementStatus(data); + + if(baekjoonJudgementStatus.isFinalResult()) { + pusherService.unsubscribeJudgement(String.format(CHANNEL_FORMAT, submitId)); + + log.info("BaekjoonJudgement : submitId: {}, status: {}", submitId, baekjoonJudgementStatus.getResult()); + + final JudgementStatus judgementStatus = JudgementStatusMapper.mapToJudgementStatus(baekjoonJudgementStatus.getResult()); + + final Integer memory = baekjoonJudgementStatus.getMemory(); + final Integer time = baekjoonJudgementStatus.getTime(); + + // JudgementStatus를 바탕으로 DB에 저장하는 로직 + // 비동기로 처리해야 PusherService의 스레드가 블로킹되지 않음 + baekjoonJudgementService.asyncUpdateJudgementStatus(submitId, judgementStatus, memory, time, BaekjoonJudgementResult.defaultResult()); + } + } + + private BaekjoonJudgementStatus parseJudgementStatus(String data) { + try { + final BaekjoonJudgementStatus baekjoonJudgementStatus = objectMapper.readValue(data, BaekjoonJudgementStatus.class); + if(baekjoonJudgementStatus != null) { + return baekjoonJudgementStatus; + } + } catch (JsonProcessingException e) { + throw new MorandiException(JudgementResultErrorCode.INVALID_JUDGEMENT_RESULT); + } + throw new MorandiException(JudgementResultErrorCode.INVALID_JUDGEMENT_RESULT); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/submit/BaekjoonSubmitStrategy.java b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/submit/BaekjoonSubmitStrategy.java new file mode 100644 index 00000000..fd96fe14 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/service/baekjoon/submit/BaekjoonSubmitStrategy.java @@ -0,0 +1,37 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.submit; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.application.service.SubmitStrategy; +import kr.co.morandi.backend.judgement.application.service.baekjoon.cookie.BaekjoonCookieManager; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit.BaekjoonSubmitApiAdapter; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class BaekjoonSubmitStrategy implements SubmitStrategy { + + private final BaekjoonSubmitApiAdapter baekjoonSubmitApiAdapter; + private final BaekjoonCookieManager baekjoonCookieManager; + + @Override + public String submit(final Long memberId, + final Language language, + final Problem problem, + final String sourceCode, + final SubmitVisibility submitVisibility, + final LocalDateTime nowDateTime) { + final String baejoonProblemId = String.valueOf(problem.getBaekjoonProblemId()); + final String cookie = baekjoonCookieManager.getCurrentMemberCookie(memberId, nowDateTime); + /* + * 제출을 하고 솔루션 아이디를 가져오는 메소드 + * */ + return baekjoonSubmitApiAdapter.submitAndGetSolutionId(baejoonProblemId, cookie, language, sourceCode, submitVisibility.getValue().toLowerCase()); + } + + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/application/usecase/submit/BaekjoonSubmitUsecase.java b/src/main/java/kr/co/morandi/backend/judgement/application/usecase/submit/BaekjoonSubmitUsecase.java new file mode 100644 index 00000000..bcbe6fa9 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/application/usecase/submit/BaekjoonSubmitUsecase.java @@ -0,0 +1,105 @@ +package kr.co.morandi.backend.judgement.application.usecase.submit; + + +import kr.co.morandi.backend.common.annotation.Usecase; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.defense_record.application.port.out.record.RecordPort; +import kr.co.morandi.backend.defense_record.domain.error.RecordErrorCode; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.defense_record.domain.model.record.Record; +import kr.co.morandi.backend.judgement.application.port.out.BaekjoonSubmitPort; +import kr.co.morandi.backend.judgement.application.request.JudgementServiceRequest; +import kr.co.morandi.backend.judgement.application.service.SubmitFacade; +import kr.co.morandi.backend.judgement.domain.event.TempCodeSaveEvent; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.exception.OAuthErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Usecase +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class BaekjoonSubmitUsecase { + + private final BaekjoonSubmitPort baekjoonSubmitPort; + private final DefenseSessionPort defenseSessionport; + private final RecordPort recordPort; + private final MemberPort memberPort; + private final SubmitFacade submitFacade; + private final ApplicationEventPublisher applicationEventPublisher; + + @Transactional + public void judgement(final JudgementServiceRequest request) { + final Long memberId = request.getMemberId(); + final Long defenseSessionId = request.getDefenseSessionId(); + final Language language = request.getLanguage(); + final String sourceCode = request.getSourceCode(); + final Long problemNumber = request.getProblemNumber(); + final SubmitVisibility submitVisibility = request.getSubmitVisibility(); + final LocalDateTime nowDateTime = request.getNowDateTime(); + + DefenseSession defenseSession = defenseSessionport.findDefenseSessionById(defenseSessionId) + .orElseThrow(() -> new MorandiException(SessionErrorCode.SESSION_NOT_FOUND)); + + Member mebmer = memberPort.findById(memberId) + .orElseThrow(() -> new MorandiException(OAuthErrorCode.MEMBER_NOT_FOUND)); + + defenseSession.validateSessionOwner(memberId); + + /* + * 시험이 종료되어 있는지 확인한다. + * */ + if (defenseSession.isTerminated()) { + throw new MorandiException(SessionErrorCode.SESSION_ALREADY_ENDED); + } + + /* + * DefenseSession에 대응하는 + * Record를 찾아온다. Fetch Join을 통해 Detail, Problem을 같이 가져온다. + * */ + final Record defenseRecord = recordPort.findRecordFetchJoinWithDetailAndProblem(defenseSession.getRecordId()) + .orElseThrow(() -> new MorandiException(RecordErrorCode.RECORD_NOT_FOUND)); + + /* + * Record가 종료되어 있는지 확인한다. + * */ + if (defenseRecord.isTerminated()) { + throw new MorandiException(RecordErrorCode.RECORD_ALREADY_TERMINATED); + } + /* + * 문제 번호로 Detail을 찾아온다. + * */ + final Detail detail = defenseRecord.getDetail(problemNumber); + /* + * 제출 기록을 저장한다. + * */ + final BaekjoonSubmit submit = BaekjoonSubmit.submit(mebmer, detail, SourceCode.of(sourceCode, language), nowDateTime, submitVisibility); + final BaekjoonSubmit savedSubmit = baekjoonSubmitPort.save(submit); + + /* + * 외부 API 요청이 트랜잭션을 잡고 있는 것을 최소화하기 위함 + * + * 채점 시작을 비동기 별도 스레드로 처리하고 + * 채점 결과를 받아서 성공하면 그 결과를 채점 기록에 저장한다. + * */ + submitFacade.asyncProcessSubmitAndSubscribeJudgement(savedSubmit.getSubmitId(), memberId, + detail.getProblem(), language, sourceCode, submitVisibility, nowDateTime); + + /* + * 비동기로 시험 채점 서비스를 호출했던 코드를 TempCode에 저장한다. + * 이 요청은 이벤트로 발행하여 TempCode 저장이 비즈니스 로직 실행에 영향을 주지 않도록 한다. + * */ + applicationEventPublisher.publishEvent(new TempCodeSaveEvent(defenseSessionId, problemNumber, sourceCode, language)); + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/error/BaekjoonCookieErrorCode.java b/src/main/java/kr/co/morandi/backend/judgement/domain/error/BaekjoonCookieErrorCode.java new file mode 100644 index 00000000..a804a788 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/error/BaekjoonCookieErrorCode.java @@ -0,0 +1,34 @@ +package kr.co.morandi.backend.judgement.domain.error; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum BaekjoonCookieErrorCode implements ErrorCode { + + INVALID_COOKIE_VALUE(HttpStatus.BAD_REQUEST, "쿠키 값을 생성하는데 필요한 정보가 부족합니다."), + ALREADY_LOGGED_OUT(HttpStatus.BAD_REQUEST, "이미 로그아웃된 쿠키입니다."), + BAEKJOON_COOKIE_NOT_FOUND(HttpStatus.BAD_REQUEST, "백준 쿠키를 찾을 수 없습니다."), + + // 관리자 + INVALID_GLOBAL_USER_ID(HttpStatus.BAD_REQUEST, "글로벌 유저 아이디는 null이거나 빈 문자열일 수 없습니다."), + INVALID_BAEKJOON_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "백준 관리자 Refresh Token은 null이거나 빈 문자열일 수 없습니다."), + NOT_EXIST_GLOBAL_COOKIE(HttpStatus.INTERNAL_SERVER_ERROR, "현재 유효한 관리 쿠키가 존재하지 않습니다. 관리자에게 문의하세요."), ; + + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/error/JudgementResultErrorCode.java b/src/main/java/kr/co/morandi/backend/judgement/domain/error/JudgementResultErrorCode.java new file mode 100644 index 00000000..f1e61757 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/error/JudgementResultErrorCode.java @@ -0,0 +1,56 @@ +package kr.co.morandi.backend.judgement.domain.error; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum JudgementResultErrorCode implements ErrorCode { + JUDGEMENT_RESULT_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 결과를 찾을 수 없습니다."), + AC_GREATER_THAN_TOT(HttpStatus.INTERNAL_SERVER_ERROR, "ac가 tot보다 큽니다."), + AC_OR_TOT_IS_NULL(HttpStatus.INTERNAL_SERVER_ERROR, "ac와 tot의 값이 null입니다."), + AC_OR_TOT_IS_NEGATIVE(HttpStatus.INTERNAL_SERVER_ERROR, "ac와 tot의 값이 음수입니다."), + + MEMORY_IS_NULL(HttpStatus.INTERNAL_SERVER_ERROR, "memory 값이 null입니다."), + MEMORY_IS_NEGATIVE(HttpStatus.INTERNAL_SERVER_ERROR, "memory 값이 음수입니다."), + + TIME_IS_NULL(HttpStatus.INTERNAL_SERVER_ERROR, "time 값이 null입니다."), + TIME_IS_NEGATIVE(HttpStatus.INTERNAL_SERVER_ERROR, "time 값이 음수입니다."), + + SUBTASK_SCORE_IS_NULL(HttpStatus.INTERNAL_SERVER_ERROR, "subtaskScore 값이 null입니다."), + SUBTASK_SCORE_IS_NEGATIVE(HttpStatus.INTERNAL_SERVER_ERROR, "subtaskScore 값이 올바르지 않습니다."), + + PARTIAL_SCORE_IS_NULL(HttpStatus.INTERNAL_SERVER_ERROR, "partialScore 값이 null입니다."), + PARTIAL_SCORE_IS_NEGATIVE(HttpStatus.INTERNAL_SERVER_ERROR, "partialScore 값이 올바르지 않습니다."), + + CORRECT_INFO_WHEN_NOT_CORRECT(HttpStatus.INTERNAL_SERVER_ERROR, "정답 상태가 아닐 경우 정답 정보는 초기상태여야 합니다."), + + RESULT_CODE_IS_NULL(HttpStatus.BAD_REQUEST, "결과 코드는 null일 수 없습니다"), + + + RESULT_INFO_WHEN_CORRECT(HttpStatus.INTERNAL_SERVER_ERROR, "정답 상태일 때는 결과 정보를 업데이트 할 수 없습니다."), + TRIAL_NUMBER_IS_NULL(HttpStatus.BAD_REQUEST, "시도 횟수는 null일 수 없습니다."), + TRIAL_NUMBER_IS_NEGATIVE(HttpStatus.BAD_REQUEST, "시도 횟수는 음수일 수 없습니다."), + ALREADY_ACCEPTED(HttpStatus.BAD_REQUEST, "이미 정답 처리된 결과입니다."), + SUBMIT_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 제출을 찾을 수 없습니다."), + INVALID_JUDGEMENT_RESULT(HttpStatus.INTERNAL_SERVER_ERROR, "백준으로부터 받은 채점 결과 응답이 올바르지 않습니다."), + ALREADY_JUDGED(HttpStatus.BAD_REQUEST, "이미 채점된 결과입니다."), + ACCEPT_MUST_HAVE_MEMORY_AND_TIME(HttpStatus.BAD_REQUEST, "정답 상태일 때는 메모리와 시간이 있어야 합니다."), + NOT_ACCEPTED_CANNOT_HAVE_MEMORY_AND_TIME(HttpStatus.BAD_REQUEST, "정답 상태가 아닐 때는 메모리와 시간이 있을 수 없습니다."); + + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/error/SubmitErrorCode.java b/src/main/java/kr/co/morandi/backend/judgement/domain/error/SubmitErrorCode.java new file mode 100644 index 00000000..e7a7b11c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/error/SubmitErrorCode.java @@ -0,0 +1,55 @@ +package kr.co.morandi.backend.judgement.domain.error; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum SubmitErrorCode implements ErrorCode { + + LANGUAGE_CODE_NOT_FOUND(HttpStatus.NOT_FOUND, "언어 코드를 찾을 수 없습니다."), + BAEKJOON_SUBMIT_PAGE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "백준 제출 페이지를 가져오는 중 오류가 발생했습니다."), + CANT_FIND_SOLUTION_ID(HttpStatus.INTERNAL_SERVER_ERROR, "제출 결과 페이지에서 솔루션 ID를 찾을 수 없습니다."), + BAEKJOON_SUBMIT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "백준 제출 중 오류가 발생했습니다."), + CSRF_KEY_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "제출 페이지에서 CSRF 키를 찾을 수 없습니다."), + REDIRECTION_LOCATION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "제출 후 리다이렉션 위치를 찾을 수 없습니다."), + + //제출 결과 저장 시 BAD_REQUEST + SOURCE_CODE_NOT_FOUND(HttpStatus.BAD_REQUEST, "제출할 소스코드가 비어있습니다."), + SUBMIT_VISIBILITY_NOT_FOUND(HttpStatus.BAD_REQUEST, "제출 공개 여부를 찾을 수 없습니다."), + PROBLEM_NUMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "문제 번호를 찾을 수 없습니다."), + DEFENSE_SESSION_NOT_FOUND(HttpStatus.NOT_FOUND, "디펜스 세션을 찾을 수 없습니다."), + LANGUAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "언어를 찾을 수 없습니다."), + VISIBILITY_NOT_NULL(HttpStatus.BAD_REQUEST, "공개 여부는 null이 될 수 없습니다."), + + DETAIL_IS_NULL(HttpStatus.BAD_REQUEST, "DEFENSE DETAIL 값이 null입니다."), + PROBLEM_NUMBER_IS_NULL(HttpStatus.BAD_REQUEST, "문제 번호가 null입니다."), + + PROBLEM_NUMBER_IS_NEGATIVE(HttpStatus.BAD_REQUEST, "문제 번호가 음수입니다."), + VISIBILITY_IS_NULL(HttpStatus.BAD_REQUEST, "공개 여부가 null입니다."), + INVALID_VISIBILITY_VALUE(HttpStatus.BAD_REQUEST, "공개 여부는 OPEN 또는 CLOSE이어야 합니다."), + SOURCE_CODE_IS_NULL(HttpStatus.BAD_REQUEST, "제출 코드가 null일 수 없습니다."), + TRIAL_NUMBER_IS_NULL(HttpStatus.BAD_REQUEST, "시도 횟수가 null일 수 없습니다."), + TRIAL_NUMBER_IS_NEGATIVE(HttpStatus.BAD_REQUEST, "시도 횟수가 음수입니다."), + SUBMIT_DATE_TIME_IS_NULL(HttpStatus.BAD_REQUEST, "제출 시간이 null일 수 없습니다."); + + + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } + + +} + diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/event/TempCodeSaveEvent.java b/src/main/java/kr/co/morandi/backend/judgement/domain/event/TempCodeSaveEvent.java new file mode 100644 index 00000000..775538a4 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/event/TempCodeSaveEvent.java @@ -0,0 +1,12 @@ +package kr.co.morandi.backend.judgement.domain.event; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import lombok.AllArgsConstructor; +import lombok.Getter; + +public record TempCodeSaveEvent( + Long defenseSessionId, + Long problemNumber, + String sourceCode, + Language language +) {} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookie.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookie.java new file mode 100644 index 00000000..f94efbc6 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookie.java @@ -0,0 +1,83 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.BaekjoonCookieErrorCode; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BaekjoonCookie { + + @Column(name = "baekjoon_cookie") + private String value; + private LocalDateTime expiredAt; + + @Enumerated(EnumType.STRING) + private CookieStatus cookieStatus; + + public void updateCookie(String cookie, LocalDateTime nowDateTime) { + this.value = validateCookie(cookie); + this.expiredAt = calculateCookieExpiredAt(nowDateTime); + this.cookieStatus = CookieStatus.LOGGED_IN; + } + public void setLoggedOut(LocalDateTime nowDateTime){ + if(this.cookieStatus == CookieStatus.LOGGED_OUT) { + throw new MorandiException(BaekjoonCookieErrorCode.ALREADY_LOGGED_OUT); + } + this.expiredAt = nowDateTime; + this.cookieStatus = CookieStatus.LOGGED_OUT; + } + + protected boolean isValidCookie(LocalDateTime nowDateTime) { + if (cookieStatus == CookieStatus.LOGGED_OUT) { + return false; + } + if (nowDateTime.isBefore(expiredAt)) { + return true; + } + logOutCookie(); + return false; + } + + private void logOutCookie() { + this.cookieStatus = CookieStatus.LOGGED_OUT; + } + + public static BaekjoonCookie of(String cookie, LocalDateTime nowDateTime) { + return BaekjoonCookie.builder() + .cookie(cookie) + .nowDateTime(nowDateTime) + .build(); + } + + private String validateCookie(String cookie) { + if(cookie != null && !cookie.trim().isEmpty()) { + return cookie; + } + throw new MorandiException(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE); + } + + private LocalDateTime calculateCookieExpiredAt(LocalDateTime cookieCreatedAt) { + if(cookieCreatedAt == null) { + throw new MorandiException(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE); + } + return cookieCreatedAt.plusHours(6); + } + + @Builder + private BaekjoonCookie(String cookie, LocalDateTime nowDateTime) { + this.value = validateCookie(cookie); + this.expiredAt = calculateCookieExpiredAt(nowDateTime); + this.cookieStatus = CookieStatus.LOGGED_IN; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookie.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookie.java new file mode 100644 index 00000000..caf31c94 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookie.java @@ -0,0 +1,57 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie; + +import jakarta.persistence.*; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.BaekjoonCookieErrorCode; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BaekjoonGlobalCookie { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long baekjoonCookieId; + + @Embedded + private BaekjoonCookie baekjoonCookie; + + private String globalUserId; + + // 백준의 Refresh Token을 저장하는 필드 + private String baekjoonRefreshToken; + + public boolean isValidCookie(LocalDateTime nowDateTime) { + return baekjoonCookie.isValidCookie(nowDateTime); + } + + public static BaekjoonGlobalCookie create(BaekjoonCookie baekjoonCookie, String globalUserId, String refreshToken) { + return new BaekjoonGlobalCookie(baekjoonCookie, globalUserId, refreshToken); + } + + @Builder + private BaekjoonGlobalCookie(BaekjoonCookie baekjoonCookie, String globalUserId, String baekjoonRefreshToken) { + validateGlobalUserId(globalUserId); + validateBaekjoonRefreshToken(baekjoonRefreshToken); + this.baekjoonCookie = baekjoonCookie; + this.globalUserId = globalUserId; + this.baekjoonRefreshToken = baekjoonRefreshToken; + } + + private void validateGlobalUserId(String globalUserId) { + if (globalUserId == null || globalUserId.trim().isEmpty()) { + throw new MorandiException(BaekjoonCookieErrorCode.INVALID_GLOBAL_USER_ID); + } + } + + private void validateBaekjoonRefreshToken(String baekjoonRefreshToken) { + if (baekjoonRefreshToken == null || baekjoonRefreshToken.trim().isEmpty()) { + throw new MorandiException(BaekjoonCookieErrorCode.INVALID_BAEKJOON_REFRESH_TOKEN); + } + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookie.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookie.java new file mode 100644 index 00000000..63907ec2 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookie.java @@ -0,0 +1,49 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie; + +import jakarta.persistence.*; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BaekjoonMemberCookie { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long baekjoonCookieId; + + @Embedded + private BaekjoonCookie baekjoonCookie; + + @OneToOne(fetch = FetchType.LAZY) + private Member member; + + public void updateCookie(String cookie, LocalDateTime nowDateTime) { + baekjoonCookie.updateCookie(cookie, nowDateTime); + } + public void setLoggedOut(LocalDateTime nowDateTime) { + baekjoonCookie.setLoggedOut(nowDateTime); + } + public boolean isValidCookie(LocalDateTime nowDateTime) { + return baekjoonCookie.isValidCookie(nowDateTime); + } + @Builder + private BaekjoonMemberCookie(String cookie, LocalDateTime nowDateTime, Member member) { + this(BaekjoonCookie.builder() + .cookie(cookie) + .nowDateTime(nowDateTime) + .build(), member); + } + + private BaekjoonMemberCookie(BaekjoonCookie baekjoonCookie, Member member) { + this.baekjoonCookie = baekjoonCookie; + this.member = member; + } + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/CookieStatus.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/CookieStatus.java new file mode 100644 index 00000000..c2c15165 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/CookieStatus.java @@ -0,0 +1,5 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie; + +public enum CookieStatus { + LOGGED_IN, LOGGED_OUT +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResult.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResult.java new file mode 100644 index 00000000..0bbbf1b7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResult.java @@ -0,0 +1,76 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.result; + +import jakarta.persistence.Embeddable; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BaekjoonJudgementResult { + + private Integer subtaskScore; + private Integer partialScore; + private Integer ac; + private Integer tot; + + private static final Integer INITIAL_SUBTASK_SCORE = 0; + private static final Integer INITIAL_PARTIAL_SCORE = 0; + private static final Integer INITIAL_AC = 0; + private static final Integer INITIAL_TOT = 0; + + public static BaekjoonJudgementResult defaultResult() { + return new BaekjoonJudgementResult(INITIAL_SUBTASK_SCORE, INITIAL_PARTIAL_SCORE, INITIAL_AC, INITIAL_TOT); + } + + public static BaekjoonJudgementResult subtaskScoreFrom(Integer subtaskScore) { + return new BaekjoonJudgementResult(subtaskScore, INITIAL_PARTIAL_SCORE, INITIAL_AC, INITIAL_TOT); + } + + public static BaekjoonJudgementResult partialScoreFrom(Integer partialScore) { + return new BaekjoonJudgementResult(INITIAL_SUBTASK_SCORE, partialScore, INITIAL_AC, INITIAL_TOT); + } + + public static BaekjoonJudgementResult acTotOf(Integer ac, Integer tot) { + return new BaekjoonJudgementResult(INITIAL_SUBTASK_SCORE, INITIAL_PARTIAL_SCORE, ac, tot); + } + + private BaekjoonJudgementResult(Integer subtaskScore, Integer partialScore, Integer ac, Integer tot) { + validateSubtaskScore(subtaskScore); + this.subtaskScore = subtaskScore; + + validatePartialScore(partialScore); + this.partialScore = partialScore; + + validateAcTot(ac, tot); + this.ac = ac; + this.tot = tot; + } + + private void validateSubtaskScore(Integer subtaskScore) { + if(subtaskScore == null) + throw new MorandiException(JudgementResultErrorCode.SUBTASK_SCORE_IS_NULL); + if(subtaskScore < 0) + throw new MorandiException(JudgementResultErrorCode.SUBTASK_SCORE_IS_NEGATIVE); + } + + private void validatePartialScore(Integer partialScore) { + if(partialScore == null) + throw new MorandiException(JudgementResultErrorCode.PARTIAL_SCORE_IS_NULL); + if(partialScore < 0) + throw new MorandiException(JudgementResultErrorCode.PARTIAL_SCORE_IS_NEGATIVE); + } + + private void validateAcTot(Integer ac, Integer tot) { + if(ac == null || tot == null) + throw new MorandiException(JudgementResultErrorCode.AC_OR_TOT_IS_NULL); + if(ac < 0 || tot < 0) + throw new MorandiException(JudgementResultErrorCode.AC_OR_TOT_IS_NEGATIVE); + if(ac > tot) + throw new MorandiException(JudgementResultErrorCode.AC_GREATER_THAN_TOT); + + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonResultType.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonResultType.java new file mode 100644 index 00000000..ca1d1f90 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonResultType.java @@ -0,0 +1,45 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.result; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +@Getter +@RequiredArgsConstructor +public enum BaekjoonResultType { + CORRECT(4, "맞았습니다!!"), + WRONG_ANSWER(6, "틀렸습니다!!"), + RUNTIME_ERROR(10, "런타임 에러"), + COMPILE_ERROR(11, "컴파일 에러"), + PROGRESS(3, "채점 중"), + COMPILE_PROGRESS(2, "컴파일 중"), + TIME_LIMIT_EXCEEDED(7, "시간 초과"), + MEMORY_LIMIT_EXCEEDED(8, "메모리 초과"), + OUTPUT_LIMIT_EXCEEDED(9, "출력 초과"), + OUTPUT_FORMAT_ERROR(5, "출력 형식이 잘못되었습니다."), + SUBMITTED(-1, "제출됨"), + OTHER(0, "Other"); + + private final int code; + private final String description; + + private static final Map valueMap = Arrays.stream(values()) + .collect(Collectors.toMap(BaekjoonResultType::getCode, e -> e)); + @JsonValue + public int getCode() { + return code; + } + @JsonCreator + public static BaekjoonResultType fromCode(Integer code) { + if(code == null) + throw new MorandiException(JudgementResultErrorCode.RESULT_CODE_IS_NULL); + return valueMap.getOrDefault(code, OTHER); + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmit.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmit.java new file mode 100644 index 00000000..4b969095 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmit.java @@ -0,0 +1,44 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.submit; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonJudgementResult; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementResult; +import kr.co.morandi.backend.judgement.domain.model.submit.Submit; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@DiscriminatorValue("BaekjoonSubmit") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BaekjoonSubmit extends Submit { + + @Embedded + private BaekjoonJudgementResult baekjoonJudgementResult; + + public static BaekjoonSubmit submit(Member member, Detail detail, SourceCode sourceCode, + LocalDateTime submitDateTime, SubmitVisibility submitVisibility) { + return new BaekjoonSubmit(member, detail, sourceCode, submitDateTime, submitVisibility, null); + } + + public void updateJudgementResult(JudgementResult judgementResult, BaekjoonJudgementResult baekjoonJudgementResult) { + super.updateJudgementResult(judgementResult); + this.baekjoonJudgementResult = baekjoonJudgementResult; + } + @Builder + private BaekjoonSubmit(Member member, Detail detail, SourceCode sourceCode, + LocalDateTime submitDateTime, SubmitVisibility submitVisibility, BaekjoonJudgementResult baekjoonJudgementResult) { + super(member, detail, sourceCode, submitDateTime, submitVisibility); + this.baekjoonJudgementResult = baekjoonJudgementResult; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementResult.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementResult.java new file mode 100644 index 00000000..916dc0ed --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementResult.java @@ -0,0 +1,76 @@ +package kr.co.morandi.backend.judgement.domain.model.submit; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class JudgementResult { + + @Enumerated(EnumType.STRING) + private JudgementStatus judgementStatus; + + @Column(name = "memory") + private Integer memory; + + @Column(name = "time") + private Integer time; + + private static final Integer INITIAL_MEMORY = 0; + private static final Integer INITIAL_TIME = 0; + + public boolean isAccepted() { + return judgementStatus.equals(JudgementStatus.ACCEPTED); + } + public static JudgementResult submit() { + return new JudgementResult(JudgementStatus.SUBMITTED, INITIAL_MEMORY, INITIAL_TIME); + } + public static JudgementResult accepted(Integer memory, Integer time) { + return new JudgementResult(JudgementStatus.ACCEPTED, memory, time); + } + public static JudgementResult rejected(JudgementStatus judgementStatus) { + return new JudgementResult(judgementStatus, INITIAL_MEMORY, INITIAL_TIME); + } + public void canUpdateJudgementResult() { + if(!judgementStatus.equals(JudgementStatus.SUBMITTED)) + throw new MorandiException(JudgementResultErrorCode.ALREADY_JUDGED); + } + @Builder + private JudgementResult(JudgementStatus judgementStatus, Integer memory, Integer time) { + if(judgementStatus == null) + throw new MorandiException(JudgementResultErrorCode.JUDGEMENT_RESULT_NOT_FOUND); + validateMemory(memory); + validateTime(time); + validateCanExistTimeAndMemory(judgementStatus, memory, time); + + this.judgementStatus = judgementStatus; + this.memory = memory; + this.time = time; + } + private void validateCanExistTimeAndMemory(JudgementStatus judgementStatus, Integer memory, Integer time) { + if(!judgementStatus.equals(JudgementStatus.ACCEPTED) && (memory != 0 || time != 0)) + throw new MorandiException(JudgementResultErrorCode.NOT_ACCEPTED_CANNOT_HAVE_MEMORY_AND_TIME); + } + + private void validateMemory(Integer memory) { + if(memory == null) + throw new MorandiException(JudgementResultErrorCode.MEMORY_IS_NULL); + if(memory < 0) + throw new MorandiException(JudgementResultErrorCode.MEMORY_IS_NEGATIVE); + } + private void validateTime(Integer time) { + if(time == null) + throw new MorandiException(JudgementResultErrorCode.TIME_IS_NULL); + if(time < 0) + throw new MorandiException(JudgementResultErrorCode.TIME_IS_NEGATIVE); + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementStatus.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementStatus.java new file mode 100644 index 00000000..4c2a1c3b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/JudgementStatus.java @@ -0,0 +1,38 @@ +package kr.co.morandi.backend.judgement.domain.model.submit; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.persistence.Column; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum JudgementStatus { + + ACCEPTED("ACCEPTED"), + PARTIALLY_ACCEPTED("PARTIALLY_ACCEPTED"), + WRONG_ANSWER("WRONG_ANSWER"), + RUNTIME_ERROR("RUNTIME_ERROR"), + COMPILE_ERROR("COMPILE_ERROR"), + TIME_LIMIT_EXCEEDED("TIME_LIMIT_EXCEEDED"), + MEMORY_LIMIT_EXCEEDED("MEMORY_LIMIT_EXCEEDED"), + SUBMITTED("SUBMITTED"); + + @Column(name = "judgement_status") + private final String value; + + @JsonCreator + public static JudgementStatus fromValue(String value) { + for(JudgementStatus status : values()) { + if(status.getValue().equals(value)) { + return status; + } + } + return null; + } + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCode.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCode.java new file mode 100644 index 00000000..e0e158bb --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCode.java @@ -0,0 +1,45 @@ +package kr.co.morandi.backend.judgement.domain.model.submit; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Embeddable +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SourceCode { + + @Column(name = "source_code", columnDefinition = "TEXT", nullable = false) + private String sourceCode; + + @Enumerated(EnumType.STRING) + private Language language; + + public void updateSourceCode(String sourceCode) { + validateLength(sourceCode); + this.sourceCode = sourceCode; + } + public static SourceCode of(String sourceCode, Language language) { + return new SourceCode(sourceCode, language); + } + + private void validateLength(String sourceCode) { + if(sourceCode == null || sourceCode.isEmpty()) + throw new MorandiException(SubmitErrorCode.SOURCE_CODE_NOT_FOUND); + } + + @Builder + private SourceCode(String sourceCode, Language language) { + validateLength(sourceCode); + this.sourceCode = sourceCode; + this.language = language; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/Submit.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/Submit.java new file mode 100644 index 00000000..dc7bc497 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/Submit.java @@ -0,0 +1,89 @@ +package kr.co.morandi.backend.judgement.domain.model.submit; + +import jakarta.persistence.*; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorColumn +@Inheritance(strategy = InheritanceType.JOINED) +public abstract class Submit extends BaseEntity { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long submitId; + + @ManyToOne(fetch = FetchType.LAZY) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + private Detail detail; + + @Embedded + private SourceCode sourceCode; + + @Enumerated(EnumType.STRING) + private SubmitVisibility submitVisibility; + + @Embedded + private JudgementResult judgementResult; + + private LocalDateTime submitDateTime; + + public void trySolveProblem() { + this.detail.trySolveProblem(this.getSubmitId(), submitDateTime); + } + + protected void updateJudgementResult(JudgementResult judgementResult) { + // 제출 하나의 결과는 한 번 정해지면 변하지 않음 + this.judgementResult.canUpdateJudgementResult(); + this.judgementResult = judgementResult; + + if(judgementResult.isAccepted()) { + this.detail.trySolveProblem(this.getSubmitId(), submitDateTime); + } + } + protected Submit(Member member, Detail detail, SourceCode sourceCode, + LocalDateTime submitDateTime, SubmitVisibility submitVisibility) { + this.member = member; + this.submitDateTime = validateSubmitDateTime(submitDateTime); + this.detail = validateDetail(detail); + this.detail.increaseSubmitCount(); + this.sourceCode = validateSubmitCode(sourceCode); + this.submitVisibility = validateSubmitVisibility(submitVisibility); + this.judgementResult = JudgementResult.submit(); + } + private LocalDateTime validateSubmitDateTime(LocalDateTime submitDateTime) { + if(submitDateTime != null) { + return submitDateTime; + } + throw new MorandiException(SubmitErrorCode.SUBMIT_DATE_TIME_IS_NULL); + } + private Detail validateDetail(Detail detail) { + if(detail != null) { + return detail; + } + throw new MorandiException(SubmitErrorCode.DETAIL_IS_NULL); + } + private SourceCode validateSubmitCode(SourceCode sourceCode) { + if(sourceCode != null) { + return sourceCode; + } + throw new MorandiException(SubmitErrorCode.SOURCE_CODE_IS_NULL); + } + private SubmitVisibility validateSubmitVisibility(SubmitVisibility submitVisibility) { + if(submitVisibility != null) { + return submitVisibility; + } + throw new MorandiException(SubmitErrorCode.VISIBILITY_NOT_NULL); + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitVisibility.java b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitVisibility.java new file mode 100644 index 00000000..a46534b7 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitVisibility.java @@ -0,0 +1,39 @@ +package kr.co.morandi.backend.judgement.domain.model.submit; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import jakarta.persistence.Column; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum SubmitVisibility { + OPEN("OPEN"), + CLOSE("CLOSE"); + + @Column(name = "submit_visibility") + private final String value; + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static SubmitVisibility fromValue(String value) { + if(value == null || value.isEmpty()) { + throw new MorandiException(SubmitErrorCode.SUBMIT_VISIBILITY_NOT_FOUND); + } + for(SubmitVisibility submitVisibility : SubmitVisibility.values()) { + if (submitVisibility.value.equals(value.toUpperCase())) { + return submitVisibility; + } + } + throw new MorandiException(SubmitErrorCode.INVALID_VISIBILITY_VALUE); + } + + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementService.java b/src/main/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementService.java new file mode 100644 index 00000000..af79be11 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementService.java @@ -0,0 +1,55 @@ +package kr.co.morandi.backend.judgement.domain.service; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.application.port.out.BaekjoonSubmitPort; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonJudgementResult; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementResult; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class BaekjoonJudgementService { + + private final BaekjoonSubmitPort baekjoonSubmitPort; + + /* + * Pusher의 결과를 받는 스레드를 블로킹하지 않기 위해 비동기로 처리하는 메서드입니다. + * TODO 실패 시 어떻게 해야할 지 + * Pusher가 단일 스레드가 아니라면 Async를 사용하지 않아도 괜찮습니다. (I/O 시 다른 스레드가 처리할 수 있기 때문) + * */ + @Async("updateJudgementStatusExecutor") + @Transactional + public void asyncUpdateJudgementStatus(final Long submitId, + final JudgementStatus judgementStatus, + final Integer memory, + final Integer time, + final BaekjoonJudgementResult baekjoonJudgementResult) { + + log.info("Update Judgement Status submitId: {}, judgementStatus: {}, memory: {}, time: {}", submitId, judgementStatus, memory, time); + + final BaekjoonSubmit submit = baekjoonSubmitPort.findSubmitJoinFetchDetailAndRecord(submitId) + .orElseThrow(() -> new MorandiException(JudgementResultErrorCode.SUBMIT_NOT_FOUND)); + + JudgementResult judgementResult = getJudgementResult(judgementStatus, memory, time); + + submit.updateJudgementResult(judgementResult, baekjoonJudgementResult); + + + baekjoonSubmitPort.save(submit); + } + private JudgementResult getJudgementResult(JudgementStatus judgementStatus, Integer memory, Integer time) { + if(judgementStatus.equals(JudgementStatus.ACCEPTED)) + return JudgementResult.accepted(memory, time); + return JudgementResult.rejected(judgementStatus); + } + + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/adapter/out/BaekjoonSubmitAdapter.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/adapter/out/BaekjoonSubmitAdapter.java new file mode 100644 index 00000000..9edd66a8 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/adapter/out/BaekjoonSubmitAdapter.java @@ -0,0 +1,25 @@ +package kr.co.morandi.backend.judgement.infrastructure.adapter.out; + +import kr.co.morandi.backend.common.annotation.Adapter; +import kr.co.morandi.backend.judgement.application.port.out.BaekjoonSubmitPort; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.infrastructure.persistence.submit.BaekjoonSubmitRepository; +import lombok.RequiredArgsConstructor; + +import java.util.Optional; + +@Adapter +@RequiredArgsConstructor +public class BaekjoonSubmitAdapter implements BaekjoonSubmitPort { + + private final BaekjoonSubmitRepository baekjoonSubmitRepository; + @Override + public BaekjoonSubmit save(BaekjoonSubmit submit) { + return baekjoonSubmitRepository.save(submit); + } + + @Override + public Optional findSubmitJoinFetchDetailAndRecord(Long submitId) { + return baekjoonSubmitRepository.findSubmitJoinFetchDetailAndRecord(submitId); + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/cookie/MorandiBaekjoonCookieManager.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/cookie/MorandiBaekjoonCookieManager.java new file mode 100644 index 00000000..dfdbf3ec --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/cookie/MorandiBaekjoonCookieManager.java @@ -0,0 +1,19 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.cookie; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +@RequiredArgsConstructor +public class MorandiBaekjoonCookieManager { + + private static final String BAEKJOON_URL = "https://www.acmicpc.net/"; + private static final String LOGIN_URL = "https://www.acmicpc.net/login"; + private final WebClient webClient; + + public String getMorandiManagedBaekjoonCookie() { + return "dummyCookie"; + } + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/result/PusherService.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/result/PusherService.java new file mode 100644 index 00000000..7eb6738b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/result/PusherService.java @@ -0,0 +1,36 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.result; + +import com.pusher.client.Pusher; +import com.pusher.client.channel.Channel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.function.BiConsumer; + +@Service +@RequiredArgsConstructor +@Slf4j +public class PusherService { + + private final Pusher pusher; + /* + * 채널에 대한 이벤트를 구독하는 로직 + * 채널에 대한 이벤트가 발생하면 콜백함수를 실행하도록 구현 + * BiConsumer를 이용하여 ChannelName과 Data를 파라미터로 받아서 콜백함수를 실행하도록 구현 + * pusher에서 bind를 수행하면, 내부의 Channel이 생기는 거고, WebSocket 연결이 추가되는 건 아닙니다. + * main WebSocket 연결은 Pusher 객체를 생성할 때 생성되는 것이고, Channel은 그 안에서 필터링 역할을 합니다. + * */ + public void subscribeJudgement(final String channelName, + final Long submitId, + final BiConsumer judgementCallback) { + Channel channel = pusher.subscribe(channelName); + // TODO : 끝난 채점은 Channel이 회수되지만, 너무 늦게 channel이 구독되어 아무 응답을 받지 못한 경우 여전히 회수되지 못하는 문제가 발생함 + channel.bind("update", pusherEvent -> + judgementCallback.accept(submitId, pusherEvent.getData())); + } + public void unsubscribeJudgement(final String channelName) { + pusher.unsubscribe(channelName); + + } +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitApiAdapter.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitApiAdapter.java new file mode 100644 index 00000000..6bc6f54e --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitApiAdapter.java @@ -0,0 +1,112 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import kr.co.morandi.backend.common.annotation.Adapter; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.net.URI; + +import static org.springframework.http.HttpHeaders.COOKIE; +import static org.springframework.http.HttpHeaders.USER_AGENT; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; + +@Adapter +@RequiredArgsConstructor +public class BaekjoonSubmitApiAdapter { + + private final WebClient webClient; + + /* + * 제출을 하고 솔루션 아이디를 가져오는 메소드 + */ + public String submitAndGetSolutionId(String baekjoonProblemId, String cookie, Language language, String sourceCode, String submitVisibility) { + String csrfKey = getCsrfKeyFromSubmitPage(cookie, baekjoonProblemId); + + final String languageCode = BaekjoonSubmitLanguageCode.getLanguageCode(language); + final String submitVisibilityCode = BaekjoonSubmitVisibility.getSubmitVisibilityCode(submitVisibility); + + MultiValueMap parameters = new LinkedMultiValueMap<>(); + parameters.add("problem_id", baekjoonProblemId); + parameters.add("language", languageCode); + parameters.add("source", sourceCode); + parameters.add("code_open", submitVisibilityCode); + parameters.add("csrf_key", csrfKey); + + + final String resultHtml = webClient.post() + .uri(String.format(BaekjoonSubmitConstants.BAEKJOON_SUBMIT_URL, baekjoonProblemId)) + .header(USER_AGENT, BaekjoonSubmitConstants.BAEKJOON_USER_AGENT) + .header(COOKIE, "OnlineJudge=" + cookie) + .contentType(APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData(parameters)) + .exchangeToMono(response -> handleRedirection(response, baekjoonProblemId)) + .block(); + + return BaekjoonSubmitHtmlParser.parseSolutionIdFromHtml(resultHtml); + } + + /* + * 제출에 필요한 csrf_key를 가져오는 메소드 + * */ + private String getCsrfKeyFromSubmitPage(String cookie, String baekjoonProblemId) { + final String submitPageHtml = webClient.get() + .uri(String.format(BaekjoonSubmitConstants.BAEKJOON_SUBMIT_URL, baekjoonProblemId)) + .header(USER_AGENT, BaekjoonSubmitConstants.BAEKJOON_USER_AGENT) + .header(HttpHeaders.COOKIE, "OnlineJudge=" + cookie) + .retrieve() + .bodyToMono(String.class) + .block(); + + return BaekjoonSubmitHtmlParser.parseCsrfKeyInSubmitPage(submitPageHtml); + } + + + private Mono handleRedirection(ClientResponse initialResponse, String baekjoonProblemId) { + /* + * 제출 후 일반적으로 3xx redirection이 발생합니다. + * 3xx redirection이 발생하면 location을 가져와서 다시 요청합니다. + * */ + if (initialResponse.statusCode().is3xxRedirection()) { + URI locationUri = initialResponse.headers().asHttpHeaders().getLocation(); + + if (locationUri == null) { + throw new MorandiException(SubmitErrorCode.REDIRECTION_LOCATION_NOT_FOUND); + } + + String location = locationUri.toString(); + + if (location == null || location.isEmpty()) { + throw new MorandiException(SubmitErrorCode.REDIRECTION_LOCATION_NOT_FOUND); + } + + if (!location.startsWith("http")) { + location = URI.create(String.format(BaekjoonSubmitConstants.BAEKJOON_SUBMIT_URL, baekjoonProblemId)) + .resolve(location) + .toString(); + } + + /* + * 직접 Redirect할 경우에는 사용자 쿠키가 필요 없습니다. + * */ + return webClient.get() + .uri(location) + .header(USER_AGENT, BaekjoonSubmitConstants.BAEKJOON_USER_AGENT) + .retrieve() + .bodyToMono(String.class); + } + + return initialResponse + .bodyToMono(String.class); + } + + +} \ No newline at end of file diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitConstants.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitConstants.java new file mode 100644 index 00000000..f526e2f6 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitConstants.java @@ -0,0 +1,9 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaekjoonSubmitConstants { + public static final String BAEKJOON_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"; + public static final String BAEKJOON_SUBMIT_URL = "https://www.acmicpc.net/submit/%s"; +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParser.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParser.java new file mode 100644 index 00000000..effa5a0f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParser.java @@ -0,0 +1,60 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.springframework.stereotype.Component; + +public class BaekjoonSubmitHtmlParser { + /* + * 제출 결과 페이지에서 솔루션 아이디를 추출하는 메소드 + * */ + public static String parseSolutionIdFromHtml(String html) { + // HTML을 파싱합니다. + Document doc = Jsoup.parse(html); + + // 테이블을 선택합니다. + Element table = doc.getElementById("status-table"); + if (table == null) { + throw new MorandiException(SubmitErrorCode.CANT_FIND_SOLUTION_ID); + } + + // 첫 번째 행을 선택합니다. + Element firstRow = table.select("tbody tr").first(); + if (firstRow == null) { + throw new MorandiException(SubmitErrorCode.CANT_FIND_SOLUTION_ID); + } + + // 첫 번째 행에서 solution-id를 추출합니다. + Element solutionIdElement = firstRow.select("td").first(); // 첫 번째 열에 있는 것이 solution-id 입니다. + + if (solutionIdElement != null && solutionIdElement.text().isEmpty()) { + throw new MorandiException(SubmitErrorCode.CANT_FIND_SOLUTION_ID); + } + + if (solutionIdElement == null) { + throw new MorandiException(SubmitErrorCode.CANT_FIND_SOLUTION_ID); + } + + return solutionIdElement.text(); + + } + + public static String parseCsrfKeyInSubmitPage(String response) { + if (response == null) { + throw new MorandiException(SubmitErrorCode.BAEKJOON_SUBMIT_PAGE_ERROR); + } + Document document = Jsoup.parse(response); + + String csrfKey = document.select("input[name=csrf_key]").attr("value"); + + if (csrfKey.isEmpty()) { + throw new MorandiException(SubmitErrorCode.CSRF_KEY_NOT_FOUND); + } + + return csrfKey; + } + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCode.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCode.java new file mode 100644 index 00000000..bcf73790 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCode.java @@ -0,0 +1,49 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum BaekjoonSubmitLanguageCode { + + JAVA("93"), + CPP("84"), + PYTHON("28"), + PYPY3("73"), + C99("0"), + RUBY("68"), + KOTLIN("69"), + SWIFT("74"), + TEXT("58"), + C_SHARP("86"), + NODE_JS("17"), + GO("12"), + D("29"), + RUST("94"), + C_PLUS17_CLANG("85"); + + private final String languageCode; + + public static String getLanguageCode(Language language) { + return BaekjoonSubmitLanguageCode.valueOf(language.name()).getLanguageCode(); + } + + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitVisibility.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitVisibility.java new file mode 100644 index 00000000..236e70b5 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitVisibility.java @@ -0,0 +1,19 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum BaekjoonSubmitVisibility { + OPEN("open"),//공개 + CLOSE("close"),//비공개 + ONLY_ACCEPTED("onlyaccepted");//맞았을 때만 공개 + + private final String submitVisibilityCode; + + public static String getSubmitVisibilityCode(String submitVisibility) { + return BaekjoonSubmitVisibility.valueOf(submitVisibility.toUpperCase()).getSubmitVisibilityCode(); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/config/PusherConfig.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/config/PusherConfig.java new file mode 100644 index 00000000..754300e8 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/config/PusherConfig.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.judgement.infrastructure.config; + +import com.pusher.client.Pusher; +import com.pusher.client.PusherOptions; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class PusherConfig { + + @Value("${pusher.appId}") + private String pusherAppId; + @Bean + public Pusher pusher() { + PusherOptions options = new PusherOptions(); + options.setCluster("ap1"); + Pusher pusher = new Pusher(pusherAppId, options); + pusher.connect(); + return pusher; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitController.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitController.java new file mode 100644 index 00000000..f2992a24 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitController.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.judgement.infrastructure.controller; + +import jakarta.validation.Valid; +import kr.co.morandi.backend.common.web.MemberId; +import kr.co.morandi.backend.judgement.application.usecase.submit.BaekjoonSubmitUsecase; +import kr.co.morandi.backend.judgement.infrastructure.controller.request.BaekjoonJudgementRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class BaekjoonSubmitController { + + private final BaekjoonSubmitUsecase baekjoonSubmitUsecase; + + @PostMapping("/submit") + public ResponseEntity submit(@Valid @RequestBody BaekjoonJudgementRequest request, + @MemberId Long memberId) { + + baekjoonSubmitUsecase.judgement(request.toServiceRequest(memberId)); + return ResponseEntity.ok() + .build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/BaekjoonMemberCookieRequest.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/BaekjoonMemberCookieRequest.java new file mode 100644 index 00000000..9d3072d3 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/BaekjoonMemberCookieRequest.java @@ -0,0 +1,15 @@ +package kr.co.morandi.backend.judgement.infrastructure.controller.cookie; + +import jakarta.validation.constraints.NotEmpty; +import kr.co.morandi.backend.judgement.application.request.cookie.BaekjoonMemberCookieServiceRequest; + +import java.time.LocalDateTime; + +public record BaekjoonMemberCookieRequest( + @NotEmpty(message = "Cookie 값은 비어 있을 수 없습니다.") + String cookie +) { + public BaekjoonMemberCookieServiceRequest toServiceRequest(Long memberId, LocalDateTime nowDateTime) { + return new BaekjoonMemberCookieServiceRequest(cookie, memberId, nowDateTime); + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieController.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieController.java new file mode 100644 index 00000000..f661c7ab --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieController.java @@ -0,0 +1,28 @@ +package kr.co.morandi.backend.judgement.infrastructure.controller.cookie; + +import jakarta.validation.Valid; +import kr.co.morandi.backend.common.web.MemberId; +import kr.co.morandi.backend.judgement.application.service.baekjoon.cookie.BaekjoonMemberCookieService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; + +@RestController +@RequiredArgsConstructor +public class CookieController { + + private final BaekjoonMemberCookieService baekjoonMemberCookieService; + + @PostMapping("/cookie/baekjoon") + public ResponseEntity saveMemberBaekjoonCookie(@Valid @RequestBody BaekjoonMemberCookieRequest request, + @MemberId Long memberId) { + baekjoonMemberCookieService.saveMemberBaekjoonCookie(request.toServiceRequest(memberId, LocalDateTime.now())); + return ResponseEntity.ok() + .build(); + } + +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/request/BaekjoonJudgementRequest.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/request/BaekjoonJudgementRequest.java new file mode 100644 index 00000000..5e295e47 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/controller/request/BaekjoonJudgementRequest.java @@ -0,0 +1,54 @@ +package kr.co.morandi.backend.judgement.infrastructure.controller.request; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.application.request.JudgementServiceRequest; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BaekjoonJudgementRequest { + + @NotNull(message = "defenseSessionId가 존재해야 합니다.") + @Positive(message = "defenseSessionId는 양수여야 합니다.") + private Long defenseSessionId; + + @NotNull(message = "problemNumber가 존재해야 합니다.") + @Positive(message = "problemNumber가 양수여야 합니다.") + private Long problemNumber; + + @NotNull(message = "language가 존재해야 합니다.") + private Language language; + + @NotEmpty(message = "sourceCode가 존재해야 합니다.") + private String sourceCode; + + @NotNull(message = "submitVisibility가 존재해야 합니다.") + private SubmitVisibility submitVisibility; + + public JudgementServiceRequest toServiceRequest(Long memberId) { + return JudgementServiceRequest.builder() + .defenseSessionId(this.getDefenseSessionId()) + .memberId(memberId) + .problemNumber(this.getProblemNumber()) + .language(this.getLanguage()) + .sourceCode(this.getSourceCode()) + .submitVisibility(this.getSubmitVisibility()) + .build(); + } + + @Builder + private BaekjoonJudgementRequest(Long defenseSessionId, Long problemNumber, Language language, String sourceCode, SubmitVisibility submitVisibility) { + this.defenseSessionId = defenseSessionId; + this.problemNumber = problemNumber; + this.language = language; + this.sourceCode = sourceCode; + this.submitVisibility = submitVisibility; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/helper/JudgementStatusMapper.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/helper/JudgementStatusMapper.java new file mode 100644 index 00000000..1de22ae8 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/helper/JudgementStatusMapper.java @@ -0,0 +1,22 @@ +package kr.co.morandi.backend.judgement.infrastructure.helper; + +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonResultType; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementStatus; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class JudgementStatusMapper { + + public static JudgementStatus mapToJudgementStatus(BaekjoonResultType baekjoonResultType) { + return switch (baekjoonResultType) { + case CORRECT -> JudgementStatus.ACCEPTED; + case WRONG_ANSWER -> JudgementStatus.WRONG_ANSWER; + case RUNTIME_ERROR -> JudgementStatus.RUNTIME_ERROR; + case COMPILE_ERROR -> JudgementStatus.COMPILE_ERROR; + case TIME_LIMIT_EXCEEDED -> JudgementStatus.TIME_LIMIT_EXCEEDED; + case MEMORY_LIMIT_EXCEEDED -> JudgementStatus.MEMORY_LIMIT_EXCEEDED; + default -> JudgementStatus.SUBMITTED; + }; + } +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepository.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepository.java new file mode 100644 index 00000000..e5ae8125 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepository.java @@ -0,0 +1,18 @@ +package kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon; + +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonGlobalCookie; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.time.LocalDateTime; +import java.util.List; + +public interface BaekjoonGlobalCookieRepository extends JpaRepository { + + @Query(""" + SELECT cookie + FROM BaekjoonGlobalCookie cookie + WHERE cookie.baekjoonCookie.expiredAt > :now + """) + List findValidGlobalCookies(LocalDateTime now); +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepository.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepository.java new file mode 100644 index 00000000..8a98ac4f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepository.java @@ -0,0 +1,10 @@ +package kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon; + +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonMemberCookie; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface BaekjoonMemberCookieRepository extends JpaRepository { + Optional findBaekjoonMemberCookieByMember_MemberId(Long memberId); +} diff --git a/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepository.java b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepository.java new file mode 100644 index 00000000..e75ba74a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepository.java @@ -0,0 +1,21 @@ +package kr.co.morandi.backend.judgement.infrastructure.persistence.submit; + +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface BaekjoonSubmitRepository extends JpaRepository { + + @Query(""" + SELECT s + FROM BaekjoonSubmit s + JOIN FETCH s.detail d + JOIN FETCH d.record + WHERE s.submitId = :submitId + """) + Optional findSubmitJoinFetchDetailAndRecord(Long submitId); +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java b/src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java index 11285144..140b6ee7 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java +++ b/src/main/java/kr/co/morandi/backend/member_management/application/port/out/member/MemberPort.java @@ -8,5 +8,6 @@ public interface MemberPort { Member saveMemberByEmail(String email, SocialType type); Member findMemberById(Long memberId); + Optional findById(Long memberId); Optional findMemberByEmail(String email); } diff --git a/src/main/java/kr/co/morandi/backend/member_management/domain/model/error/MemberErrorCode.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/error/MemberErrorCode.java new file mode 100644 index 00000000..71fc148f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/error/MemberErrorCode.java @@ -0,0 +1,23 @@ +package kr.co.morandi.backend.member_management.domain.model.error; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum MemberErrorCode implements ErrorCode { + MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "사용자를 찾을 수 없습니다"), + ; + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + @Override + public String getMessage() { + return message; + } +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java index 9528c18d..b6d9ed7f 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java +++ b/src/main/java/kr/co/morandi/backend/member_management/domain/model/member/Member.java @@ -2,8 +2,11 @@ import jakarta.persistence.*; import kr.co.morandi.backend.common.model.BaseEntity; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonMemberCookie; import lombok.*; +import java.time.LocalDateTime; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -25,6 +28,22 @@ public class Member extends BaseEntity { private String profileImageURL; private String description; + + @OneToOne(mappedBy = "member", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private BaekjoonMemberCookie baekjoonMemberCookie; + + public void saveBaekjoonCookie(String cookie, LocalDateTime nowDateTime) { + if(baekjoonMemberCookie == null) { + baekjoonMemberCookie = BaekjoonMemberCookie.builder() + .cookie(cookie) + .nowDateTime(nowDateTime) + .member(this) + .build(); + return; + } + baekjoonMemberCookie.updateCookie(cookie, nowDateTime); + } + @Builder private Member(String nickname, String baekjoonId, String email, SocialType socialType, String profileImageURL, String description) { diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java index 222b8e28..9f1f96cb 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/adapter/member/MemberAdapter.java @@ -29,5 +29,9 @@ public Member findMemberById(Long memberId) { return memberRepository.findById(memberId) .orElseThrow(() -> new MorandiException(OAuthErrorCode.MEMBER_NOT_FOUND)); } + @Override + public Optional findById(Long memberId) { + return memberRepository.findById(memberId); + } } diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java index c1e0da80..fcc2f4c5 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepository.java @@ -2,10 +2,19 @@ import kr.co.morandi.backend.member_management.domain.model.member.Member; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; public interface MemberRepository extends JpaRepository { Boolean existsByNickname(String nickname); Optional findByEmail(String email); + + @Query(""" + select m + from Member m + left join fetch m.baekjoonMemberCookie + where m.memberId = :memberId + """) + Optional findMemberJoinFetchCookie(Long memberId); } diff --git a/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java index 6cd0b8a8..d8570158 100644 --- a/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java +++ b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java @@ -1,11 +1,16 @@ package kr.co.morandi.backend; +import com.fasterxml.jackson.databind.ObjectMapper; import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; import kr.co.morandi.backend.defense_information.infrastructure.controller.DailyDefenseController; import kr.co.morandi.backend.defense_management.application.service.message.DefenseMessageService; import kr.co.morandi.backend.defense_management.infrastructure.controller.SessionConnectionController; import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; import kr.co.morandi.backend.defense_record.infrastructure.controller.DailyRecordController; +import kr.co.morandi.backend.judgement.application.service.baekjoon.cookie.BaekjoonMemberCookieService; +import kr.co.morandi.backend.judgement.application.usecase.submit.BaekjoonSubmitUsecase; +import kr.co.morandi.backend.judgement.infrastructure.controller.BaekjoonSubmitController; +import kr.co.morandi.backend.judgement.infrastructure.controller.cookie.CookieController; import kr.co.morandi.backend.member_management.infrastructure.config.cookie.utils.CookieUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; @@ -20,7 +25,9 @@ @WebMvcTest(controllers = { DailyDefenseController.class, DailyRecordController.class, - SessionConnectionController.class + SessionConnectionController.class, + CookieController.class, + BaekjoonSubmitController.class }, excludeAutoConfiguration = SecurityAutoConfiguration.class, excludeFilters = { @@ -35,6 +42,9 @@ public abstract class ControllerTestSupport { @Autowired protected MockMvc mockMvc; + @Autowired + protected ObjectMapper objectMapper; + // DailyDefenseController @MockBean protected DailyDefenseUseCase dailyDefenseUseCase; @@ -50,4 +60,13 @@ public abstract class ControllerTestSupport { @MockBean protected DefenseMessageService defenseMessageService; + // CookieController + @MockBean + protected BaekjoonMemberCookieService baekjoonMemberCookieService; + + // BaekjoonSubmitController + @MockBean + protected BaekjoonSubmitUsecase baekjoonSubmitUsecase; + + } diff --git a/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java b/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java index cc5ed845..99ec4e93 100644 --- a/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java +++ b/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java @@ -1,9 +1,15 @@ package kr.co.morandi.backend; +import kr.co.morandi.backend.config.WebClientTestConfig; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; import org.springframework.test.context.ActiveProfiles; @ActiveProfiles("test") @SpringBootTest +@Import({ + WebClientTestConfig.class +}) public abstract class IntegrationTestSupport { + } diff --git a/src/test/java/kr/co/morandi/backend/config/WebClientTestConfig.java b/src/test/java/kr/co/morandi/backend/config/WebClientTestConfig.java new file mode 100644 index 00000000..d69edaff --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/config/WebClientTestConfig.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.config; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import org.springframework.web.reactive.function.client.WebClient; + +import static org.mockito.Mockito.mock; + +@TestConfiguration +public class WebClientTestConfig { + @Bean + @Primary + WebClient testWebClient(ExchangeFunction exchangeFunction) { + return WebClient.builder() + .exchangeFunction(exchangeFunction) + .build(); + } + + /* + * 중요 + * + * 내부에서 WebClient를 이용하는 통합 테스트에서는 ExchangeFunction의 exchange 메서드를 + * Stubbing하여 테스트를 진행합니다. + * + * 내부적으로 API가 호출되는 횟수만큼 Stubbing을 해주어야 합니다. + * */ + @Bean + ExchangeFunction exchangeFunction() { + return mock(ExchangeFunction.class); + } +} diff --git a/src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java b/src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java index 8068edc0..37b13458 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/application/service/DailyDefenseUseCaseImplTest.java @@ -8,6 +8,8 @@ import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.factory.TestBaekjoonSubmitFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; @@ -77,7 +79,6 @@ void getDailyDefenseInfo() { ) ); } - @DisplayName("사용자가 있을 때 응시 기록도 있다면 DailyDefense 정보를 조회하면 isSolved는 True/False를 포함하여 반환한다.") @Test void getDailyDefenseInfoWithMemberAndRecord() { @@ -88,7 +89,9 @@ void getDailyDefenseInfoWithMemberAndRecord() { LocalDateTime requestTime = LocalDateTime.of(2021, 10, 1, 12, 0, 0); final DailyRecord dailyRecord = createDailyRecord(dailyDefense, member, 2L, requestTime); - dailyRecord.solveProblem(2L, "example", requestTime.plusHours(1)); + + BaekjoonSubmit 제출 = TestBaekjoonSubmitFactory.createSubmit(member, dailyRecord.getDetail(2L), requestTime.plusHours(1)); + 제출.trySolveProblem(); dailyRecordPort.saveDailyRecord(dailyRecord); // when diff --git a/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDetailTest.java index d0b912ed..7b041c81 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/dailydefense/DailyDetailTest.java @@ -80,7 +80,7 @@ void initialSubmitCountIsZero() { // then assertThat(DailyDefenseProblemRecord.getSubmitCount()).isZero(); } - @DisplayName("DailyDefenseProblemRecord가 생성되면 solvedCode는 null이다") + @DisplayName("DailyDefenseProblemRecord가 생성되면 correctSubmit은 null이다") @Test void initialSolvedCodeIsSetToNull() { // given @@ -96,8 +96,8 @@ void initialSolvedCodeIsSetToNull() { DailyDetail DailyDefenseProblemRecord = DailyDetail.create(member, 1L, problem, DailyDefenseRecord, DailyDefense); // then - assertThat(DailyDefenseProblemRecord.getSolvedCode()) - .isEqualTo(null); + assertThat(DailyDefenseProblemRecord.getCorrectSubmitId()) + .isNull(); } private DailyDefense createDailyDefense() { diff --git a/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDetailTest.java index 5bb0a65c..3ecc1b92 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/randomdefense/RandomDetailTest.java @@ -78,7 +78,7 @@ void initialSubmitCountIsZero() { // then assertThat(randomDefenseProblemRecord.getSubmitCount()).isZero(); } - @DisplayName("RandomDefenseProblemRecord가 생성되면 solvedCode는 null이다") + @DisplayName("RandomDefenseProblemRecord가 생성되면 correctSubmit은 null이다") @Test void initialSolvedCodeIsSetToNull() { // given @@ -91,8 +91,8 @@ void initialSolvedCodeIsSetToNull() { RandomDetail randomDefenseProblemRecord = RandomDetail.create(member, 1L, problem, randomDefenseRecord, randomDefense); // then - assertThat(randomDefenseProblemRecord.getSolvedCode()) - .isEqualTo(null); + assertThat(randomDefenseProblemRecord.getCorrectSubmitId()) + .isNull(); } private Problem createProblem() { diff --git a/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDetailTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDetailTest.java index 7ae3dc42..97047f50 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDetailTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/model/stagedefense/StageDetailTest.java @@ -30,7 +30,7 @@ void initialSolvedTimeIsZero() { StageDetail stageDefenseProblemRecord = StageDetail.create(member,1L, problem, stageDefenseRecord, randomStageDefense); // then - assertThat(stageDefenseProblemRecord.getSolvedTime()).isEqualTo(0L); + assertThat(stageDefenseProblemRecord.getSolvedTime()).isZero(); } @DisplayName("원하는 스테이지 번호에 따른 스테이지 문제 기록을 만들 수 있다.") @@ -97,8 +97,8 @@ void initialSolvedCodeIsNull() { // then assertThat(stageDefenseProblemRecord) - .extracting("solvedCode") - .isEqualTo(null); + .extracting("correctSubmitId") + .isNull(); } @DisplayName("스테이지 문제 기록을 생성할 수 있다.") diff --git a/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java index b060f6f6..ab243707 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/domain/service/dailydefense/DailyDefenseGenerationServiceTest.java @@ -3,14 +3,12 @@ import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseProblemRepository; -import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @@ -19,30 +17,18 @@ import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static org.assertj.core.api.Assertions.assertThat; +@Transactional class DailyDefenseGenerationServiceTest extends IntegrationTestSupport { @Autowired private DailyDefenseGenerationService dailyDefenseGenerationService; - - @Autowired - private DailyDefensePort dailyDefensePort; @Autowired - private DailyDefenseRepository dailyDefenseRepository; + private DailyDefensePort dailyDefensePort; - @Autowired - private DailyDefenseProblemRepository dailyDefenseProblemRepository; - @Autowired private ProblemRepository problemRepository; - @AfterEach - void tearDown() { - dailyDefenseProblemRepository.deleteAllInBatch(); - dailyDefenseRepository.deleteAllInBatch(); - problemRepository.deleteAllInBatch(); - } - @DisplayName("오늘의 문제를 생성할 수 있다.") @Test void generateDailyDefense() { diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java index e2d5413f..1cd12d65 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/service/message/DefenseMessageServiceTest.java @@ -13,11 +13,17 @@ import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +import reactor.core.publisher.Mono; import java.time.LocalDate; import java.time.LocalDateTime; @@ -53,6 +59,34 @@ class DefenseMessageServiceTest extends IntegrationTestSupport { private DefenseSessionRepository defenseSessionRepository; + /* + * 내부에서 WebClient를 이용하는 통합 테스트에서는 ExchangeFunction의 exchange 메서드를 + * Stubbing하여 테스트를 진행합니다. + * + * 내부적으로 API가 호출되는 횟수만큼 Stubbing을 해주어야 합니다. + * */ + @Autowired + private ExchangeFunction exchangeFunction; + + @BeforeEach + void setUp() { + Mockito.when(exchangeFunction.exchange(Mockito.any())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "application/json") + .body(""" + [ + { + "baekjoonProblemId": 1000, + "title": "A+B" + }, + { + "baekjoonProblemId": 1001, + "title": "A-B" + } + ]""") + .build())); + } + @DisplayName("다른 사람의 디펜스 세션에 연결을 요청하면 예외가 발생한다.") @Test void getConnectionToOtherUserDefense() { diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/judgement/JudgementResultSubscriberTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/judgement/JudgementResultSubscriberTest.java new file mode 100644 index 00000000..1e2b8625 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/judgement/JudgementResultSubscriberTest.java @@ -0,0 +1,29 @@ +package kr.co.morandi.backend.defense_management.domain.model.judgement; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.judgement.application.service.baekjoon.result.JudgementResultSubscriber; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class JudgementResultSubscriberTest extends IntegrationTestSupport { + + @Autowired + private JudgementResultSubscriber judgementResultSubscriber; + + @DisplayName("") + @Test + void test() throws InterruptedException { + // given + + // when +// for(int i = 79196920;i<79197000;i++){ +// judgementResultService.subscribeJudgement(String.valueOf(i), ); + + + // then +// Thread.sleep(100000L); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java index 8b96f2ec..2677e933 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSessionTest.java @@ -3,8 +3,12 @@ import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.TempCode; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.factory.TestDefenseFactory; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.factory.TestProblemFactory; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; @@ -17,11 +21,13 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.CPP; +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; @@ -30,6 +36,81 @@ @ActiveProfiles("test") class DefenseSessionTest { + @DisplayName("DefenseSession에서 해당하는 문제의 TempCode를 업데이트할 수 있다.") + @Test + void updateTempCode() { + // given + Member 사용자 = TestMemberFactory.createMember(); + + Map 문제 = TestProblemFactory.createProblems(5); + + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord 오늘의_문제_기록 = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + DefenseSession 디펜스_세션 = DefenseSession.builder() + .member(사용자) + .defenseType(오늘의_문제.getDefenseType()) + .problemNumbers(Set.of(1L)) + .recordId(오늘의_문제_기록.getRecordId()) + .startDateTime(LocalDateTime.of(2021, 1, 1, 0, 0)) + .endDateTime(LocalDateTime.of(2021, 1, 1, 1, 0)) + .build(); + + // when + 디펜스_세션.updateTempCode(1L, JAVA, "code"); + + // then + assertThat(디펜스_세션.getSessionDetail(1L) + .getTempCode(JAVA)) + .extracting("code", "language") + .containsExactly("code", JAVA); + + } + + @DisplayName("끝난 DefenseSession에서 TempCode를 업데이트하려고 하면 예외를 발생한다.") + @Test + void updateTempCodeWhenTerminated() { + // given + Member 사용자 = TestMemberFactory.createMember(); + + Map 문제 = TestProblemFactory.createProblems(5); + + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord 오늘의_문제_기록 = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + DefenseSession 디펜스_세션 = DefenseSession.builder() + .member(사용자) + .defenseType(오늘의_문제.getDefenseType()) + .problemNumbers(Set.of(1L)) + .recordId(오늘의_문제_기록.getRecordId()) + .startDateTime(LocalDateTime.of(2021, 1, 1, 0, 0)) + .endDateTime(LocalDateTime.of(2021, 1, 1, 1, 0)) + .build(); + + 디펜스_세션.terminateSession(); + + // when & then + assertThatThrownBy(() -> 디펜스_세션.updateTempCode(1L, JAVA, "code")) + .isInstanceOf(MorandiException.class) + .hasMessage(SessionErrorCode.SESSION_ALREADY_ENDED.getMessage()); + } + @DisplayName("세션 소유자일 경우 아무 예외가 발생하지 않는다.") @Test void validateSessionOwner() { diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/LanguageTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/LanguageTest.java new file mode 100644 index 00000000..a9fa3534 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/model/tempcode/model/LanguageTest.java @@ -0,0 +1,63 @@ +package kr.co.morandi.backend.defense_management.domain.model.tempcode.model; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.domain.error.LanguageErrorCode; +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.assertThatThrownBy; + +class LanguageTest { + @DisplayName("CPP에 해당하는 Language 객체를 반환한다.") + @Test + void fromCpp() { + // given + String value = "CPP"; + + // when + Language language = Language.from(value); + + // then + assertThat(language).isEqualTo(Language.CPP); + } + + @DisplayName("JAVA 해당하는 Language 객체를 반환한다.") + @Test + void fromJava() { + // given + String value = "JAVA"; + + // when + Language language = Language.from(value); + + // then + assertThat(language).isEqualTo(Language.JAVA); + } + + @DisplayName("PYTHON 해당하는 Language 객체를 반환한다.") + @Test + void fromPython() { + // given + String value = "PYTHON"; + + // when + Language language = Language.from(value); + + // then + assertThat(language).isEqualTo(Language.PYTHON); + } + + @DisplayName("적절하지 않은 Language 값을 입력하면 예외를 반환한다.") + @Test + void fromInvalidValue() { + // given + String value = "JAVAGOOD"; + + // when & then + assertThatThrownBy(() -> Language.from(value)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining(LanguageErrorCode.LANGUAGE_NOT_FOUND.getMessage()); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java index 7f616e14..6ff350d4 100644 --- a/src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/SessionServiceTest.java @@ -149,10 +149,10 @@ private DailyRecord tryDailyDefense(LocalDateTime today, Member member) { final Map problem = getProblem(dailyDefense, 2L); DailyRecord dailyRecord = DailyRecord.builder() - .testDate(today) + .date(today) .defense(dailyDefense) - .status(RecordStatus.IN_PROGRESS) .member(member) + .problems(problem) .build(); final DailyRecord savedDailyRecord = dailyRecordRepository.save(dailyRecord); @@ -161,7 +161,7 @@ private DailyRecord tryDailyDefense(LocalDateTime today, Member member) { .member(member) .problemNumber(1L) .problem(problem.get(1L)) - .record(savedDailyRecord) + .records(savedDailyRecord) .defense(dailyDefense) .build(); diff --git a/src/test/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveServiceTest.java new file mode 100644 index 00000000..b8b66c16 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/domain/service/TempCodeSaveServiceTest.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_management.domain.service; + +class TempCodeSaveServiceTest { + + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepositoryTest.java new file mode 100644 index 00000000..8539dab1 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/persistence/session/DefenseSessionRepositoryTest.java @@ -0,0 +1,97 @@ +package kr.co.morandi.backend.defense_management.infrastructure.persistence.session; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.factory.TestDefenseFactory; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.factory.TestProblemFactory; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class DefenseSessionRepositoryTest extends IntegrationTestSupport { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @Autowired + private DefenseSessionRepository defenseSessionRepository; + + @DisplayName("디펜스 세션을 FetchJoin을 통해 TempCode까지 한 번에 조회할 수 있다.") + @Test + void findDefenseSessionJoinFetch() { + // given + Member 사용자 = TestMemberFactory.createMember(); + memberRepository.save(사용자); + + Map 문제 = TestProblemFactory.createProblems(5); + problemRepository.saveAll(문제.values()); + + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + dailyDefenseRepository.save(오늘의_문제); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord 오늘의_문제_기록 = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + dailyRecordRepository.save(오늘의_문제_기록); + + DefenseSession 디펜스_세션 = DefenseSession.builder() + .member(사용자) + .defenseType(오늘의_문제.getDefenseType()) + .problemNumbers(Set.of(1L)) + .recordId(오늘의_문제_기록.getRecordId()) + .startDateTime(LocalDateTime.of(2021, 1, 1, 0, 0)) + .endDateTime(LocalDateTime.of(2021, 1, 1, 1, 0)) + .build(); + + 디펜스_세션.updateTempCode(1L, JAVA, "exampleCode"); + + defenseSessionRepository.save(디펜스_세션); + + // when + Optional defenseSession = defenseSessionRepository.findDefenseSessionJoinFetchTempCode(디펜스_세션.getDefenseSessionId()); + + // then + assertThat(defenseSession).isPresent() + .get() + .extracting("defenseSessionId", "defenseType") + .contains(디펜스_세션.getDefenseSessionId(), 오늘의_문제.getDefenseType()); + assertThat(defenseSession.get().getSessionDetail(1L).getTempCode(JAVA)) + .extracting("language", "code") + .contains(JAVA, "exampleCode"); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/application/usecase/DailyRecordRankUseCaseTest.java b/src/test/java/kr/co/morandi/backend/defense_record/application/usecase/DailyRecordRankUseCaseTest.java index a27cec36..cd0a0e8a 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/application/usecase/DailyRecordRankUseCaseTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/application/usecase/DailyRecordRankUseCaseTest.java @@ -8,6 +8,9 @@ import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.factory.TestBaekjoonSubmitFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.infrastructure.persistence.submit.BaekjoonSubmitRepository; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; @@ -48,6 +51,9 @@ class DailyRecordRankUseCaseTest extends IntegrationTestSupport { @Autowired private MemberRepository memberRepository; + @Autowired + private BaekjoonSubmitRepository baekjoonSubmitRepository; + @DisplayName("특정 시점 DailyRecord의 순위를 조회할 수 있다.") @Test void getDailyRecordsRankByDate() { @@ -74,13 +80,23 @@ void getDailyRecordsRankByDate() { * * -> 등수 = 2 -> 1 -> 3 * */ - dailyRecord1.solveProblem(1L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 15)); + BaekjoonSubmit 제출1 = TestBaekjoonSubmitFactory.createSubmit(member1, dailyRecord1.getDetail(1L), LocalDateTime.of(2021, 10, 1, 0, 15)); + final BaekjoonSubmit 저장된_제출1 = baekjoonSubmitRepository.save(제출1); + 저장된_제출1.trySolveProblem(); + + BaekjoonSubmit 제출2 = TestBaekjoonSubmitFactory.createSubmit(member2, dailyRecord2.getDetail(2L), LocalDateTime.of(2021, 10, 1, 0, 30)); + final BaekjoonSubmit 저장된_제출2 = baekjoonSubmitRepository.save(제출2); + 저장된_제출2.trySolveProblem(); - dailyRecord2.solveProblem(2L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 30)); dailyRecord2.tryMoreProblem(getProblem(dailyDefense, 3L)); - dailyRecord2.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 45)); - dailyRecord3.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 1, 0)); + BaekjoonSubmit 제출3 = TestBaekjoonSubmitFactory.createSubmit(member2, dailyRecord2.getDetail(3L), LocalDateTime.of(2021, 10, 1, 0, 45)); + final BaekjoonSubmit 저장된_제출3 = baekjoonSubmitRepository.save(제출3); + 저장된_제출3.trySolveProblem(); + + BaekjoonSubmit 제출4 = TestBaekjoonSubmitFactory.createSubmit(member3, dailyRecord3.getDetail(3L), LocalDateTime.of(2021, 10, 1, 1, 0)); + final BaekjoonSubmit 저장된_제출4 = baekjoonSubmitRepository.save(제출4); + 저장된_제출4.trySolveProblem(); dailyRecordRepository.saveAll(List.of(dailyRecord1, dailyRecord2, dailyRecord3, dailyRecord4, dailyRecord5)); diff --git a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecordTest.java index 390029c9..7ff54fe2 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/customdefense_record/CustomRecordTest.java @@ -53,7 +53,7 @@ void solvedCountIsZero() { CustomRecord customDefenseRecord = CustomRecord.create(customDefense, member, startTime, problems); // then - assertThat(customDefenseRecord.getSolvedCount()).isZero(); + assertThat(customDefenseRecord.getTotalSolvedCount()).isZero(); } @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 총 문제 수 기록은 커스텀 디펜스 문제 수와 같아야 한다.") @Test @@ -151,7 +151,7 @@ void submitCountIsZero() { ); } - @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 세부 문제 기록의 문제 정답 코드는 null 값 이어야 한다.") + @DisplayName("커스텀 디펜스 기록이 만들어졌을 때 세부 문제 기록의 정답 제출 id는 null 값 이어야 한다.") @Test void solvedCodeIsNull() { // given @@ -165,7 +165,7 @@ void solvedCodeIsNull() { // when & then assertThat(customDefenseRecord.getDetails()) - .extracting("solvedCode") + .extracting("correctSubmitId") .containsExactlyInAnyOrder( null, null diff --git a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java index 35a270db..ec0ae0f5 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/dailydefense_record/DailyRecordTest.java @@ -3,6 +3,8 @@ import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.factory.TestBaekjoonSubmitFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; import org.junit.jupiter.api.DisplayName; @@ -72,12 +74,12 @@ void solveProblem() { DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); // when - dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 15, 0)); - + BaekjoonSubmit 제출 = TestBaekjoonSubmitFactory.createSubmit(member, dailyRecord.getDetail(2L), LocalDateTime.of(2024, 3, 1, 12, 15, 0)); + 제출.trySolveProblem(); // then assertThat(dailyRecord) - .extracting("totalSolvedTime", "solvedCount") + .extracting("totalSolvedTime", "totalSolvedCount") .contains( 15 * 60L, 1L ); @@ -87,7 +89,6 @@ void solveProblem() { tuple(true, 15 * 60L) ); } - @DisplayName("이미 정답처리된 문제를 정답 solved하려하면 바뀌지 않는다.") @Test void solveProblemWhenAlreadySolved() { @@ -97,16 +98,18 @@ void solveProblemWhenAlreadySolved() { Member member = createMember("user"); Map triedProblem = getProblems(dailyDefense, 2L); DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); - dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 15, 0)); - // when - dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 20, 0)); + BaekjoonSubmit 제출 = TestBaekjoonSubmitFactory.createSubmit(member, dailyRecord.getDetail(2L), LocalDateTime.of(2024, 3, 1, 12, 15, 0)); + 제출.trySolveProblem(); + // when + BaekjoonSubmit 제출2 = TestBaekjoonSubmitFactory.createSubmit(member, dailyRecord.getDetail(2L), LocalDateTime.of(2024, 3, 1, 12, 20, 0)); + 제출2.trySolveProblem(); // then assertThat(dailyRecord) - .extracting("totalSolvedTime", "solvedCount") + .extracting("totalSolvedTime", "totalSolvedCount") .contains( 15 * 60L, 1L ); @@ -126,7 +129,9 @@ void getSolvedProblemNumbers() { Map triedProblem = getProblems(dailyDefense, 2L); DailyRecord dailyRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, triedProblem); dailyRecord.tryMoreProblem(getProblems(dailyDefense, 3L)); - dailyRecord.solveProblem(2L, "solvedCode", LocalDateTime.of(2024, 3, 1, 12, 15, 0)); + + BaekjoonSubmit 제출 = TestBaekjoonSubmitFactory.createSubmit(member, dailyRecord.getDetail(2L), LocalDateTime.of(2024, 3, 1, 12, 15, 0)); + 제출.trySolveProblem(); // when final Set solvedProblemNumbers = dailyRecord.getSolvedProblemNumbers(); @@ -204,7 +209,7 @@ void solvedCountIsZero() { DailyRecord dailyDefenseRecord = DailyRecord.tryDefense(startTime, dailyDefense, member, problems); // then - assertThat(dailyDefenseRecord.getSolvedCount()).isZero(); + assertThat(dailyDefenseRecord.getTotalSolvedCount()).isZero(); } @DisplayName("오늘의 문제 기록이 만들어진 시점이 문제가 출제된 시점에서 하루 이상 넘어가면 예외가 발생한다.") @@ -276,7 +281,7 @@ void submitCountIsZero() { .extracting("submitCount") .containsExactlyInAnyOrder(0L); } - @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 코드는 null 이어야 한다.") + @DisplayName("오늘의 문제 테스트 기록이 만들어졌을 때 세부 문제들의 정답 제출은 null 이어야 한다.") @Test void solvedCodeIsNull() { // given @@ -290,7 +295,7 @@ void solvedCodeIsNull() { // then assertThat(dailyDefenseRecord.getDetails()) - .extracting("solvedCode") + .extracting("correctSubmitId") .contains((String)null); } private Map getProblems(DailyDefense DailyDefense, Long problemNumber) { diff --git a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecordTest.java index 7d913e4c..3a8c55ad 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/randomdefense_record/RandomRecordTest.java @@ -32,7 +32,7 @@ void solvedCountIsZero() { RandomRecord randomDefenseRecord = RandomRecord.create(randomDefense, member, now, problems); // then - assertThat(randomDefenseRecord.getSolvedCount()).isZero(); + assertThat(randomDefenseRecord.getTotalSolvedCount()).isZero(); } @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 총 문제 수는 랜덤 디펜스 문제 개수와 같아야 한다.") @Test @@ -129,7 +129,7 @@ void submitCountIsZero() { 0L ); } - @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 세부 문제 기록의 정답 코드는 모두 null 이어야 한다.") + @DisplayName("랜덤 디펜스 기록이 만들어졌을 때 세부 문제 기록의 정답 제출 id는 모두 null 이어야 한다.") @Test void solvedCodeIsNull() { // given @@ -144,7 +144,7 @@ void solvedCodeIsNull() { // then assertThat(randomDefenseRecord.getDetails()) - .extracting("solvedCode") + .extracting("correctSubmitId") .containsExactly( null, null, diff --git a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordTest.java new file mode 100644 index 00000000..ef879b54 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/record/RecordTest.java @@ -0,0 +1,152 @@ +package kr.co.morandi.backend.defense_record.domain.model.record; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ActiveProfiles("test") +class RecordTest { + + @DisplayName("ProblemNumber로 Problem을 찾아올 수 있다.") + @Test + void getProblem() { + DailyDefense 오늘의_문제 = createDailyDefense(); + LocalDateTime 시험_시작_시간 = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member 시험_보려는_사용자 = createMember("user"); + + Long 문제_번호 = 1L; + Map problems = getProblems(오늘의_문제, 문제_번호); + + final DailyRecord dailyRecord = DailyRecord.tryDefense(시험_시작_시간, 오늘의_문제, 시험_보려는_사용자, problems); + + // when + final Problem problem = dailyRecord.getProblem(문제_번호); + + // then + assertThat(problem.getBaekjoonProblemId()) + .isEqualTo(문제_번호); + + } + + @DisplayName("Invalid ProblemNumber로 Problem을 찾으려고 하면 예외가 발생한다.") + @Test + void getProblemWithInvalidProblemNumber() { + // given + DailyDefense 오늘의_문제 = createDailyDefense(); + LocalDateTime 시험_시작_시간 = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member 시험_보려는_사용자 = createMember("user"); + Long 문제_번호 = 1L; + Map problems = getProblems(오늘의_문제, 문제_번호); + + final DailyRecord dailyRecord = DailyRecord.tryDefense(시험_시작_시간, 오늘의_문제, 시험_보려는_사용자, problems); + + Long 잘못된_문제_번호 = 2L; + + // when & then + assertThatThrownBy(() -> dailyRecord.getProblem(잘못된_문제_번호)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining("해당 번호의 문제 풀이 기록을 찾을 수 없습니다."); + + } + @DisplayName("ProblemNumber로 Detail을 찾아올 수 있다.") + @Test + void getDetail() { + // given + DailyDefense 오늘의_문제 = createDailyDefense(); + LocalDateTime 시험_시작_시간 = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member 시험_보려는_사용자 = createMember("user"); + + Long 문제_번호 = 1L; + Map problems = getProblems(오늘의_문제, 문제_번호); + + final DailyRecord dailyRecord = DailyRecord.tryDefense(시험_시작_시간, 오늘의_문제, 시험_보려는_사용자, problems); + + // when + final DailyDetail detail = dailyRecord.getDetail(문제_번호); + + // then + assertThat(detail.getProblem().getBaekjoonProblemId()) + .isEqualTo(문제_번호); + + } + + @DisplayName("Invalid ProblemNumber로 Detail을 찾으려고 하면 예외가 발생한다.") + @Test + void getDetailWithInvalidProblemNumber() { + // given + DailyDefense 오늘의_문제 = createDailyDefense(); + LocalDateTime 시험_시작_시간 = LocalDateTime.of(2024, 3, 1, 12, 0, 0); + Member 시험_보려는_사용자 = createMember("user"); + Long 문제_번호 = 1L; + Map problems = getProblems(오늘의_문제, 문제_번호); + + final DailyRecord dailyRecord = DailyRecord.tryDefense(시험_시작_시간, 오늘의_문제, 시험_보려는_사용자, problems); + + Long 잘못된_문제_번호 = 2L; + + // when & then + assertThatThrownBy(() -> dailyRecord.getDetail(잘못된_문제_번호)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining("해당 번호의 문제 풀이 기록을 찾을 수 없습니다."); + + } + + private Map getProblems(DailyDefense DailyDefense, Long problemNumber) { + return DailyDefense.getDailyDefenseProblems().stream() + .filter(p -> p.getProblemNumber().equals(problemNumber)) + .collect(Collectors.toMap(DailyDefenseProblem::getProblemNumber, DailyDefenseProblem::getProblem)); + } + private DailyDefense createDailyDefense() { + AtomicLong problemNumber = new AtomicLong(1L); + Map problemMap = createProblems().stream() + .collect(Collectors.toMap(p -> problemNumber.getAndIncrement(), problem -> problem)); + LocalDate createdDate = LocalDate.of(2024, 3, 1); + return DailyDefense.create(createdDate, "오늘의 문제 테스트", problemMap); + } + private List createProblems() { + Problem problem1 = Problem.builder() + .baekjoonProblemId(1L) + .problemTier(B5) + .solvedCount(0L) + .build(); + Problem problem2 = Problem.builder() + .baekjoonProblemId(2L) + .problemTier(S5) + .solvedCount(0L) + .build(); + Problem problem3 = Problem.builder() + .baekjoonProblemId(3L) + .problemTier(G5) + .solvedCount(0L) + .build(); + return List.of(problem1, problem2, problem3); + } + private Member createMember(String name) { + return Member.builder() + .email(name + "@gmail.com") + .socialType(GOOGLE) + .nickname(name) + .build(); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecordTest.java b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecordTest.java index 9d80eab5..80bac8b5 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecordTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/domain/model/stagedefense_record/StageRecordTest.java @@ -153,8 +153,8 @@ void solvedCodeIsNull() { // then assertThat(stageDefenseRecord.getDetails()) - .extracting("solvedCode") - .containsExactly((String)null); + .extracting("correctSubmitId") + .contains((String) null); } private StageDefense createRandomStageDefense() { RandomCriteria.DifficultyRange bronzeRange = RandomCriteria.DifficultyRange.of(B5, B1); diff --git a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java index 33b0b957..3493090b 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/adapter/record/RecordAdapterTest.java @@ -6,6 +6,7 @@ import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyDetail; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; import kr.co.morandi.backend.defense_record.domain.model.record.Record; import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; import kr.co.morandi.backend.member_management.domain.model.member.Member; @@ -47,6 +48,24 @@ class RecordAdapterTest extends IntegrationTestSupport { @Autowired private DailyRecordRepository dailyRecordRepository; + @DisplayName("RecordId로 Record를 찾아올 수 있다. (Fetch Join)") + @Test + void findRecordByIdFetchDetails() { + LocalDateTime today = LocalDateTime.of(2021, 10, 1, 0, 0); + + final Member member = createMember(); + final DailyRecord dailyRecord = tryDailyDefense(today, member); + + // when + final Optional> record = recordAdapter.findRecordFetchJoinWithDetail(dailyRecord.getRecordId()); + + // then + assertThat(record).isPresent() + .get() + .extracting("recordId", "defense.contentName", "details") + .contains(dailyRecord.getRecordId(), "오늘의 문제 테스트", dailyRecord.getDetails()); + } + @DisplayName("RecordId로 Record를 찾아올 수 있다.") @Test void findRecordById() { @@ -57,7 +76,7 @@ void findRecordById() { final DailyRecord dailyRecord = tryDailyDefense(today, member); // when - final Optional> record = recordAdapter.findRecordById(dailyRecord.getRecordId()); + final Optional> record = recordAdapter.findRecordById(dailyRecord.getRecordId()); // then assertThat(record).isPresent() @@ -72,9 +91,10 @@ private DailyRecord tryDailyDefense(LocalDateTime today, Member member) { final Map problem = getProblem(dailyDefense, 2L); DailyRecord dailyRecord = DailyRecord.builder() - .testDate(today) + .date(today) .defense(dailyDefense) .member(member) + .problems(problem) .build(); final DailyRecord savedDailyRecord = dailyRecordRepository.save(dailyRecord); @@ -83,7 +103,7 @@ private DailyRecord tryDailyDefense(LocalDateTime today, Member member) { .member(member) .problemNumber(1L) .problem(problem.get(1L)) - .record(savedDailyRecord) + .records(savedDailyRecord) .defense(dailyDefense) .build(); diff --git a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java index d60a3986..28d86ac0 100644 --- a/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_record/infrastructure/persistence/dailydefense_record/DailyRecordRepositoryTest.java @@ -5,6 +5,12 @@ import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.factory.TestBaekjoonSubmitFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; +import kr.co.morandi.backend.judgement.domain.model.submit.Submit; +import kr.co.morandi.backend.judgement.infrastructure.persistence.submit.BaekjoonSubmitRepository; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; @@ -27,9 +33,13 @@ import java.util.stream.Collectors; import static kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier.*; +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; +import static kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility.CLOSE; import static kr.co.morandi.backend.member_management.domain.model.member.SocialType.GOOGLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @Transactional class DailyRecordRepositoryTest extends IntegrationTestSupport { @@ -46,6 +56,11 @@ class DailyRecordRepositoryTest extends IntegrationTestSupport { @Autowired private MemberRepository memberRepository; + @Autowired + private BaekjoonSubmitRepository baekjoonSubmitRepository; + + + @DisplayName("특정 시점 DailyRecord의 순위를 조회할 수 있다.") @Test void getDailyRecordsRankByDate() { @@ -68,16 +83,26 @@ void getDailyRecordsRankByDate() { * * -> 등수 = 2 -> 1 -> 3 * */ - dailyRecord1.solveProblem(1L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 15)); - dailyRecord2.solveProblem(2L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 30)); + BaekjoonSubmit 제출1 = TestBaekjoonSubmitFactory.createSubmit(member1, dailyRecord1.getDetail(1L), LocalDateTime.of(2021, 10, 1, 0, 15)); + final BaekjoonSubmit 저장된_제출1 = baekjoonSubmitRepository.save(제출1); + 저장된_제출1.trySolveProblem(); + + BaekjoonSubmit 제출2 = TestBaekjoonSubmitFactory.createSubmit(member2, dailyRecord2.getDetail(2L), LocalDateTime.of(2021, 10, 1, 0, 30)); + final BaekjoonSubmit 저장된_제출2 = baekjoonSubmitRepository.save(제출2); + 저장된_제출2.trySolveProblem(); + dailyRecord2.tryMoreProblem(getProblem(dailyDefense, 3L)); - dailyRecord2.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 0, 45)); - dailyRecord3.solveProblem(3L, "exampleCode", LocalDateTime.of(2021, 10, 1, 1, 0)); + BaekjoonSubmit 제출3 = TestBaekjoonSubmitFactory.createSubmit(member2, dailyRecord2.getDetail(3L), LocalDateTime.of(2021, 10, 1, 0, 45)); + final BaekjoonSubmit 저장된_제출3 = baekjoonSubmitRepository.save(제출3); + 저장된_제출3.trySolveProblem(); - dailyRecordRepository.saveAll(List.of(dailyRecord1, dailyRecord2, dailyRecord3)); + BaekjoonSubmit 제출4 = TestBaekjoonSubmitFactory.createSubmit(member3, dailyRecord3.getDetail(3L), LocalDateTime.of(2021, 10, 1, 1, 0)); + final BaekjoonSubmit 저장된_제출4 = baekjoonSubmitRepository.save(제출4); + 저장된_제출4.trySolveProblem(); + dailyRecordRepository.saveAll(List.of(dailyRecord1, dailyRecord2, dailyRecord3)); // when Pageable pageable = PageRequest.of(0, 5); @@ -85,7 +110,7 @@ void getDailyRecordsRankByDate() { // then assertThat(dailyRecords).hasSize(3) - .extracting(DailyRecord::getMember, DailyRecord::getSolvedCount, DailyRecord::getTotalSolvedTime) + .extracting(DailyRecord::getMember, DailyRecord::getTotalSolvedCount, DailyRecord::getTotalSolvedTime) .containsExactly(// 푼 시간은 초단위 tuple(member2, 2L, 75L * 60), tuple(member1, 1L, 15L * 60), diff --git a/src/test/java/kr/co/morandi/backend/docs/cookie/CookieControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/cookie/CookieControllerDocsTest.java new file mode 100644 index 00000000..e59c37f3 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/docs/cookie/CookieControllerDocsTest.java @@ -0,0 +1,54 @@ +package kr.co.morandi.backend.docs.cookie; + +import kr.co.morandi.backend.docs.RestDocsSupport; +import kr.co.morandi.backend.judgement.application.service.baekjoon.cookie.BaekjoonMemberCookieService; +import kr.co.morandi.backend.judgement.infrastructure.controller.cookie.BaekjoonMemberCookieRequest; +import kr.co.morandi.backend.judgement.infrastructure.controller.cookie.CookieController; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class CookieControllerDocsTest extends RestDocsSupport { + + private final BaekjoonMemberCookieService cookieService = mock(BaekjoonMemberCookieService.class); + @Override + protected Object initController() { + return new CookieController(cookieService); + } + + @DisplayName("백준 쿠키를 저장하는 API") + @Test + void saveMemberBaekjoonCookie() throws Exception { + BaekjoonMemberCookieRequest request = new BaekjoonMemberCookieRequest("cookie"); + + doNothing().when(cookieService).saveMemberBaekjoonCookie(any()); + + final ResultActions perform = mockMvc.perform(post("/cookie/baekjoon") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))); + + perform + .andExpect(status().isOk()) + .andDo(document("save-member-baekjoon-cookie", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + fieldWithPath("cookie").type(JsonFieldType.STRING) + .description("백준 쿠키") + ) + )); + } +} diff --git a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java index b657fcc9..f4b3189e 100644 --- a/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java +++ b/src/test/java/kr/co/morandi/backend/docs/dailydefense/DailyDefenseControllerDocsTest.java @@ -24,7 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -public class DailyDefenseControllerDocsTest extends RestDocsSupport { +class DailyDefenseControllerDocsTest extends RestDocsSupport { private final DailyDefenseUseCase dailyDefenseUseCase = mock(DailyDefenseUseCase.class); @Override diff --git a/src/test/java/kr/co/morandi/backend/docs/submit/BaekjoonSubmitControllerDocsTest.java b/src/test/java/kr/co/morandi/backend/docs/submit/BaekjoonSubmitControllerDocsTest.java new file mode 100644 index 00000000..3aee45fd --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/docs/submit/BaekjoonSubmitControllerDocsTest.java @@ -0,0 +1,78 @@ +package kr.co.morandi.backend.docs.submit; + +import com.fasterxml.jackson.core.JsonProcessingException; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.docs.RestDocsSupport; +import kr.co.morandi.backend.judgement.application.usecase.submit.BaekjoonSubmitUsecase; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.judgement.infrastructure.controller.BaekjoonSubmitController; +import kr.co.morandi.backend.judgement.infrastructure.controller.request.BaekjoonJudgementRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class BaekjoonSubmitControllerDocsTest extends RestDocsSupport { + + private final BaekjoonSubmitUsecase baekjoonSubmitUsecase = mock(BaekjoonSubmitUsecase.class); + @Override + protected Object initController() { + return new BaekjoonSubmitController(baekjoonSubmitUsecase); + } + + @DisplayName("백준 제출 API") + @Test + void submit() throws Exception { + Long defenseSessionId = 1L; + Long problemNumber = 1L; + Language language = Language.JAVA; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isOk()) + .andDo(document("submit-for-judgement", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestFields( + fieldWithPath("defenseSessionId").type(JsonFieldType.NUMBER) + .description("디펜스 세션 아이디"), + fieldWithPath("problemNumber").type(JsonFieldType.NUMBER) + .description("문제 번호"), + fieldWithPath("language").type(JsonFieldType.STRING) + .description("사용 언어 [JAVA/CPP/PYTHON] "), + fieldWithPath("sourceCode").type(JsonFieldType.STRING) + .description("제출 소스 코드"), + fieldWithPath("submitVisibility").type(JsonFieldType.STRING) + .description("제출 공개 여부 [OPEN/CLOSE]") + ))); + } + +} diff --git a/src/test/java/kr/co/morandi/backend/factory/TestBaekjoonSubmitFactory.java b/src/test/java/kr/co/morandi/backend/factory/TestBaekjoonSubmitFactory.java new file mode 100644 index 00000000..917a01a8 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/factory/TestBaekjoonSubmitFactory.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.factory; + +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; +import kr.co.morandi.backend.member_management.domain.model.member.Member; + +import java.time.LocalDateTime; + +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; +import static kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility.CLOSE; + +public class TestBaekjoonSubmitFactory { + public static BaekjoonSubmit createSubmit(Member member, Detail detail, LocalDateTime submitTime) { + return BaekjoonSubmit.builder() + .submitDateTime(submitTime) + .submitVisibility(CLOSE) + .member(member) + .detail(detail) + .sourceCode(SourceCode.builder() + .sourceCode("sourceCode") + .language(JAVA) + .build()) + .build(); + } +} diff --git a/src/test/java/kr/co/morandi/backend/factory/TestDefenseFactory.java b/src/test/java/kr/co/morandi/backend/factory/TestDefenseFactory.java new file mode 100644 index 00000000..0d4dd953 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/factory/TestDefenseFactory.java @@ -0,0 +1,20 @@ +package kr.co.morandi.backend.factory; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefenseProblem; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; + +import java.time.LocalDate; +import java.util.Map; + +public class TestDefenseFactory { + + public static DailyDefense createDailyDefense(Map problems) { + + return DailyDefense.builder() + .problems(problems) + .date(LocalDate.of(2021, 1, 1)) + .contentName("contentName") + .build(); + } +} diff --git a/src/test/java/kr/co/morandi/backend/factory/TestMemberFactory.java b/src/test/java/kr/co/morandi/backend/factory/TestMemberFactory.java new file mode 100644 index 00000000..6bbb5038 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/factory/TestMemberFactory.java @@ -0,0 +1,14 @@ +package kr.co.morandi.backend.factory; + +import kr.co.morandi.backend.member_management.domain.model.member.Member; + +public class TestMemberFactory { + public static Member createMember() { + return Member.builder() + .nickname("nickname") + .baekjoonId("baekjoonId") + .email("email") + .build(); + } +} + diff --git a/src/test/java/kr/co/morandi/backend/factory/TestProblemFactory.java b/src/test/java/kr/co/morandi/backend/factory/TestProblemFactory.java new file mode 100644 index 00000000..3f4bb83d --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/factory/TestProblemFactory.java @@ -0,0 +1,42 @@ +package kr.co.morandi.backend.factory; + +import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.domain.model.problem.ProblemStatus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TestProblemFactory { + + public static Problem createProblem() { + return Problem.builder() + .problemStatus(ProblemStatus.ACTIVE) + .baekjoonProblemId(1000L) + .problemTier(ProblemTier.S5) + .solvedCount(0L) + .build(); + } + + static Map tierMap = Map.of(1, ProblemTier.S5, + 2, ProblemTier.G4, + 3, ProblemTier.G3, + 4, ProblemTier.G1, + 5, ProblemTier.B4); + + public static Map createProblems(int count) { + Map problems = new HashMap(); + for (int i = 0; i < count; i++) { + problems.put((long) (i + 1), Problem.builder() + .problemStatus(ProblemStatus.ACTIVE) + .baekjoonProblemId(1000L + i) + .problemTier(tierMap.get(i % 5 + 1)) + .solvedCount(0L) + .build()); + } + return problems; + } + +} diff --git a/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieServiceTest.java b/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieServiceTest.java new file mode 100644 index 00000000..f78d378f --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/cookie/BaekjoonMemberCookieServiceTest.java @@ -0,0 +1,84 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.cookie; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.judgement.application.request.cookie.BaekjoonMemberCookieServiceRequest; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonMemberCookie; +import kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon.BaekjoonMemberCookieRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class BaekjoonMemberCookieServiceTest extends IntegrationTestSupport { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private BaekjoonMemberCookieService baekjoonMemberCookieService; + + @Autowired + private BaekjoonMemberCookieRepository baekjoonMemberCookieRepository; + + @DisplayName("이미 쿠키가 저장돼있는 경우에도 쿠키를 갱신할 수 있다.") + @Test + void saveMemberBaekjoonCookie_이미_쿠키가_저장돼있는_경우() { + // given + LocalDateTime 과거시각 = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + String 과거_쿠키 = "dummyCookie"; + Member 사용자 = TestMemberFactory.createMember(); + 사용자.saveBaekjoonCookie(과거_쿠키, 과거시각); + final Member 저장된_사용자 = memberRepository.save(사용자); + + String 새로운_쿠키 = "newDummyCookie"; + LocalDateTime 현재시각 = LocalDateTime.of(2021, 1, 2, 0, 0, 0); + + final BaekjoonMemberCookieServiceRequest 서비스_요청 = new BaekjoonMemberCookieServiceRequest(새로운_쿠키, 저장된_사용자.getMemberId(), 현재시각); + + // when + baekjoonMemberCookieService.saveMemberBaekjoonCookie(서비스_요청); + + // then + final Optional 저장된_쿠키 = baekjoonMemberCookieRepository.findBaekjoonMemberCookieByMember_MemberId(저장된_사용자.getMemberId()); + + assertThat(저장된_쿠키).isPresent() + .get() + .extracting("baekjoonCookie.value", "baekjoonCookie.expiredAt") + .contains("newDummyCookie", 현재시각.plusHours(6)); + + } + + @DisplayName("사용자의 쿠키를 저장할 수 있다.") + @Test + void saveMemberBaekjoonCookie() { + // given + LocalDateTime 현재시각 = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + String 쿠키 = "dummyCookie"; + Member 사용자 = TestMemberFactory.createMember(); + + final Member 저장된_사용자 = memberRepository.save(사용자); + + final BaekjoonMemberCookieServiceRequest 서비스_요청 = new BaekjoonMemberCookieServiceRequest(쿠키, 저장된_사용자.getMemberId(), 현재시각); + + // when + baekjoonMemberCookieService.saveMemberBaekjoonCookie(서비스_요청); + + // then + final Optional 저장된_쿠키 = baekjoonMemberCookieRepository.findBaekjoonMemberCookieByMember_MemberId(저장된_사용자.getMemberId()); + + assertThat(저장된_쿠키).isPresent() + .get() + .extracting("baekjoonCookie.value", "baekjoonCookie.expiredAt") + .contains(쿠키, 현재시각.plusHours(6)); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatusTest.java b/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatusTest.java new file mode 100644 index 00000000..4a60bce8 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonJudgementStatusTest.java @@ -0,0 +1,278 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.result; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonResultType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.junit.jupiter.api.Assertions.*; + +class BaekjoonJudgementStatusTest extends IntegrationTestSupport { + + @Autowired + private ObjectMapper objectMapper; + + @DisplayName("컴파일 에러 테스트") + @Test + void testDeserializationCompileError() throws JsonProcessingException { + String json = "{\"result\":11,\"solution_id\":79195594}"; + + BaekjoonJudgementStatus result = objectMapper.readValue(json, BaekjoonJudgementStatus.class); + + assertEquals(BaekjoonResultType.COMPILE_ERROR, result.getResult()); + assertNull(result.getProgress()); + assertNull(result.getMemory()); + assertNull(result.getTime()); + assertNull(result.getSubtaskScore()); + assertNull(result.getPartialScore()); + assertNull(result.getAc()); + assertNull(result.getTot()); + assertNull(result.getFeedback()); + assertNull(result.getRteReason()); + assertNull(result.getRemain()); + } + + @DisplayName("런타임 에러 테스트") + @Test + void testDeserializationRuntimeError() throws JsonProcessingException { + String json = "{\"result\":10,\"solution_id\":79195594}"; + + BaekjoonJudgementStatus result = objectMapper.readValue(json, BaekjoonJudgementStatus.class); + + assertEquals(BaekjoonResultType.RUNTIME_ERROR, result.getResult()); + assertNull(result.getProgress()); + assertNull(result.getMemory()); + assertNull(result.getTime()); + assertNull(result.getSubtaskScore()); + assertNull(result.getPartialScore()); + assertNull(result.getAc()); + assertNull(result.getTot()); + assertNull(result.getFeedback()); + assertNull(result.getRteReason()); + assertNull(result.getRemain()); + } + + @DisplayName("프로그레스 97% 테스트") + @Test + void testDeserializationProgress97() throws JsonProcessingException { + String json = "{\"progress\":97,\"result\":3,\"solution_id\":79195594}"; + + BaekjoonJudgementStatus result = objectMapper.readValue(json, BaekjoonJudgementStatus.class); + + assertEquals(BaekjoonResultType.PROGRESS, result.getResult()); + assertEquals(97, result.getProgress()); + assertNull(result.getMemory()); + assertNull(result.getTime()); + assertNull(result.getSubtaskScore()); + assertNull(result.getPartialScore()); + assertNull(result.getAc()); + assertNull(result.getTot()); + assertNull(result.getFeedback()); + assertNull(result.getRteReason()); + assertNull(result.getRemain()); + } + + @DisplayName("정답입니다 테스트") + @Test + void testDeserializationFinalResult() throws JsonProcessingException { + String json = "{\"memory\":739436,\"result\":4,\"solution_id\":79195594,\"time\":3944}"; + + BaekjoonJudgementStatus result = objectMapper.readValue(json, BaekjoonJudgementStatus.class); + + assertEquals(BaekjoonResultType.CORRECT, result.getResult()); + assertEquals(739436, result.getMemory()); + assertEquals(3944, result.getTime()); + assertNull(result.getProgress()); + assertNull(result.getSubtaskScore()); + assertNull(result.getPartialScore()); + assertNull(result.getAc()); + assertNull(result.getTot()); + assertNull(result.getFeedback()); + assertNull(result.getRteReason()); + assertNull(result.getRemain()); + } + + @DisplayName("결과가 WRONG_ANSWER일 때 isRejected가 true를 반환해야 한다") + @Test + void givenWrongAnswer_whenCheckingIsRejected_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.WRONG_ANSWER) + .build(); + + // when + boolean isRejected = judgementStatus.isRejected(); + + // then + assertTrue(isRejected); + } + + @DisplayName("결과가 RUNTIME_ERROR일 때 isRejected가 true를 반환해야 한다") + @Test + void givenRuntimeError_whenCheckingIsRejected_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.RUNTIME_ERROR) + .build(); + + // when + boolean isRejected = judgementStatus.isRejected(); + + // then + assertTrue(isRejected); + } + + @DisplayName("결과가 COMPILE_ERROR일 때 isRejected가 true를 반환해야 한다") + @Test + void givenCompileError_whenCheckingIsRejected_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.COMPILE_ERROR) + .build(); + + // when + boolean isRejected = judgementStatus.isRejected(); + + // then + assertTrue(isRejected); + } + + @DisplayName("결과가 TIME_LIMIT_EXCEEDED일 때 isRejected가 true를 반환해야 한다") + @Test + void givenTimeLimitExceeded_whenCheckingIsRejected_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.TIME_LIMIT_EXCEEDED) + .build(); + + // when + boolean isRejected = judgementStatus.isRejected(); + + // then + assertTrue(isRejected); + } + + @DisplayName("결과가 OTHER일 때 isRejected가 true를 반환해야 한다") + @Test + void givenOther_whenCheckingIsRejected_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.OTHER) + .build(); + + // when + boolean isRejected = judgementStatus.isRejected(); + + // then + assertTrue(isRejected); + } + + @DisplayName("결과가 CORRECT일 때 isRejected가 false를 반환해야 한다") + @Test + void givenCorrect_whenCheckingIsRejected_thenShouldReturnFalse() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.CORRECT) + .build(); + + // when + boolean isRejected = judgementStatus.isRejected(); + + // then + assertFalse(isRejected); + } + + @DisplayName("결과가 CORRECT일 때 isFinalResult가 true를 반환해야 한다") + @Test + void givenCorrect_whenCheckingIsFinalResult_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.CORRECT) + .build(); + + // when + boolean isFinalResult = judgementStatus.isFinalResult(); + + // then + assertTrue(isFinalResult); + } + + @DisplayName("결과가 WRONG_ANSWER일 때 isFinalResult가 true를 반환해야 한다") + @Test + void givenWrongAnswer_whenCheckingIsFinalResult_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.WRONG_ANSWER) + .build(); + + // when + boolean isFinalResult = judgementStatus.isFinalResult(); + + // then + assertTrue(isFinalResult); + } + + @DisplayName("결과가 RUNTIME_ERROR일 때 isFinalResult가 true를 반환해야 한다") + @Test + void givenRuntimeError_whenCheckingIsFinalResult_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.RUNTIME_ERROR) + .build(); + + // when + boolean isFinalResult = judgementStatus.isFinalResult(); + + // then + assertTrue(isFinalResult); + } + + @DisplayName("결과가 COMPILE_ERROR일 때 isFinalResult가 true를 반환해야 한다") + @Test + void givenCompileError_whenCheckingIsFinalResult_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.COMPILE_ERROR) + .build(); + + // when + boolean isFinalResult = judgementStatus.isFinalResult(); + + // then + assertTrue(isFinalResult); + } + + @DisplayName("결과가 TIME_LIMIT_EXCEEDED일 때 isFinalResult가 true를 반환해야 한다") + @Test + void givenTimeLimitExceeded_whenCheckingIsFinalResult_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.TIME_LIMIT_EXCEEDED) + .build(); + + // when + boolean isFinalResult = judgementStatus.isFinalResult(); + + // then + assertTrue(isFinalResult); + } + + @DisplayName("결과가 OTHER일 때 isFinalResult가 true를 반환해야 한다") + @Test + void givenOther_whenCheckingIsFinalResult_thenShouldReturnTrue() { + // given + BaekjoonJudgementStatus judgementStatus = BaekjoonJudgementStatus.builder() + .result(BaekjoonResultType.OTHER) + .build(); + + // when + boolean isFinalResult = judgementStatus.isFinalResult(); + + // then + assertTrue(isFinalResult); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonResultTypeTest.java b/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonResultTypeTest.java new file mode 100644 index 00000000..1fc78730 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/application/service/baekjoon/result/BaekjoonResultTypeTest.java @@ -0,0 +1,117 @@ +package kr.co.morandi.backend.judgement.application.service.baekjoon.result; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonResultType; +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.assertThatThrownBy; + +class BaekjoonResultTypeTest { + + @DisplayName("Null이 들어올 때 예외가 발생해야 한다.") + @Test + void fromCodeWithNullCode() { + // given + Integer code = null; + + // when & then + assertThatThrownBy(() -> BaekjoonResultType.fromCode(code)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.RESULT_CODE_IS_NULL.getMessage()); + } + + @DisplayName("정답입니다 테스트") + @Test + void fromCode4() { + // given + int code = 4; + + // when + BaekjoonResultType result = BaekjoonResultType.fromCode(code); + + // then + assertThat(result) + .extracting("code", "description") + .contains(4, "맞았습니다!!"); + } + + @DisplayName("틀렸습니다 테스트") + @Test + void fromCode6() { + // given + int code = 6; + + // when + BaekjoonResultType result = BaekjoonResultType.fromCode(code); + + // then + assertThat(result) + .extracting("code", "description") + .contains(6, "틀렸습니다!!"); + + } + @DisplayName("런타임 에러 테스트") + @Test + void fromCode10() { + // given + int code = 10; + + // when + BaekjoonResultType result = BaekjoonResultType.fromCode(code); + + // then + assertThat(result) + .extracting("code", "description") + .contains(10, "런타임 에러"); + } + + @DisplayName("컴파일 에러 테스트") + @Test + void fromCode11() { + // given + int code = 11; + + // when + BaekjoonResultType result = BaekjoonResultType.fromCode(code); + + // then + assertThat(result) + .extracting("code", "description") + .contains(11, "컴파일 에러"); + + } + + @DisplayName("채점 중 테스트") + @Test + void fromCode3() { + // given + int code = 3; + + // when + BaekjoonResultType result = BaekjoonResultType.fromCode(code); + + // then + assertThat(result) + .extracting("code", "description") + .contains(3, "채점 중"); + } + + @DisplayName("Other 테스트") + @Test + void fromCode0() { + // given + int code = 0; + + // when + BaekjoonResultType result = BaekjoonResultType.fromCode(code); + + // then + assertThat(result) + .extracting("code", "description") + .contains(0, "Other"); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookieTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookieTest.java new file mode 100644 index 00000000..3a6d8b42 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonCookieTest.java @@ -0,0 +1,280 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.BaekjoonCookieErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BaekjoonCookieTest { + + @DisplayName("백준 쿠키를 빈 값으로 업데이트 할 수 없다.") + @Test + void updateCookieWithEmptyValue() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + String 새로운_쿠키 = " "; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + // when & then + assertThatThrownBy(() -> baekjoonCookie.updateCookie(새로운_쿠키, 다시_로그인_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + + } + + @DisplayName("백준 쿠키를 null 값으로 업데이트 할 수 없다.") + @Test + void updateCookieWithNullValue() { + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + String 새로운_쿠키 = null; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + // when & then + assertThatThrownBy(() -> baekjoonCookie.updateCookie(새로운_쿠키, 다시_로그인_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + } + + @DisplayName("백준 쿠키를 업데이트하는 시간이 null인 경우 업데이트 할 수 없다.") + @Test + void updateCookieWithNullNowDateTime() { + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + String 새로운_쿠키 = "newCookie"; + LocalDateTime 다시_로그인_시간 = null; + // when & then + assertThatThrownBy(() -> baekjoonCookie.updateCookie(새로운_쿠키, 다시_로그인_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + } + + @DisplayName("백준 쿠키 상태를 로그아웃 상태로 변경할 수 있다.") + @Test + void setLoggedOut() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + LocalDateTime 로그아웃_시간 = LocalDateTime.of(2021, 1, 1, 6, 0); + + // when + baekjoonCookie.setLoggedOut(로그아웃_시간); + + // then + assertThat(baekjoonCookie) + .extracting("cookieStatus", "expiredAt") + .contains(CookieStatus.LOGGED_OUT, 로그아웃_시간); + + } + + @DisplayName("이미 로그아웃 상태인 백준 쿠키는 다시 로그아웃 상태로 변경할 수 없다.") + @Test + void setLoggedOutWhenAlreadyLoggedOut() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + LocalDateTime 로그아웃_시간 = LocalDateTime.of(2021, 1, 1, 6, 0); + baekjoonCookie.setLoggedOut(로그아웃_시간); + + // when & then + assertThatThrownBy(() -> baekjoonCookie.setLoggedOut(로그아웃_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.ALREADY_LOGGED_OUT.getMessage()); + } + + @DisplayName("백준 쿠키를 만료된 상태에서 유효한 상태로 변경할 수 있다.") + @Test + void updateCookieWhenExpired() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + baekjoonCookie.setLoggedOut(LocalDateTime.of(2021, 1, 1, 6, 0)); + + String 새로운_쿠키 = "newCookie"; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + + // when + baekjoonCookie.updateCookie(새로운_쿠키, 다시_로그인_시간); + + // then + assertThat(baekjoonCookie) + .extracting("cookieStatus", "expiredAt") + .contains(CookieStatus.LOGGED_IN, 다시_로그인_시간.plusHours(6)); + } + + @DisplayName("백준 쿠키를 만료된 상태에서 유효한 상태로 변경할 수 있다.") + @Test + void updateCookie() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + String 새로운_쿠키 = "newCookie"; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + + // when + baekjoonCookie.updateCookie(새로운_쿠키, 다시_로그인_시간); + + // then + assertThat(baekjoonCookie) + .extracting("cookieStatus", "expiredAt") + .contains(CookieStatus.LOGGED_IN, 다시_로그인_시간.plusHours(6)); + + } + + @DisplayName("백준 쿠키가 유효한지 확인할 수 있다.") + @Test + void validateCookie() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + // when + boolean isValidCookie = baekjoonCookie.isValidCookie(LocalDateTime.of(2021, 1, 1, 3, 0)); + + // then + assertThat(isValidCookie).isTrue(); + + } + @DisplayName("로그아웃된 백준 쿠키는 Valid하지 않다.") + @Test + void validateCookieWhenLoggedOut() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + BaekjoonCookie 백준_쿠키 = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + LocalDateTime 만료된_시간 = LocalDateTime.of(2021, 1, 1, 6, 0); + 백준_쿠키.setLoggedOut(만료된_시간); + + + // when + final boolean validCookie = 백준_쿠키.isValidCookie(LocalDateTime.of(2021, 1, 1, 3, 0)); + + // then + assertThat(validCookie).isFalse(); + + } + + @DisplayName("만료 시간이 지난 경우 백준 쿠키는 Valid하지 않다.") + @Test + void validateCookieWhenExpired() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + BaekjoonCookie baekjoonCookie = BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + LocalDateTime 만료된_시간 = LocalDateTime.of(2021, 1, 1, 6, 0); + + // when + final boolean validCookie = baekjoonCookie.isValidCookie(만료된_시간); + + // then + assertThat(validCookie).isFalse(); + + } + + @DisplayName("빈 쿠키값으로 백준 쿠키를 생성할 수 없다.") + @Test + void createBaekjoonCookieWithInvalidCookieValue() { + // given + String 쿠키 = " "; + + // when & then + assertThatThrownBy(() -> BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(LocalDateTime.of(2021, 1, 1, 0, 0)) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + } + + @DisplayName("null인 쿠키값으로 백준 쿠키를 생성할 수 없다.") + @Test + void createBaekjoonCookieWithNullCookieValue() { + // given + String 쿠키 = null; + + // when & then + assertThatThrownBy(() -> BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(LocalDateTime.of(2021, 1, 1, 0, 0)) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + } + + @DisplayName("현재 시간이 null인 경우 백준 쿠키를 생성할 수 없다.") + @Test + void createBaekjoonCookieWithNullNowDateTime() { + // given + String 쿠키 = "testCookie"; + LocalDateTime 등록된_시간 = null; + + // when & then + assertThatThrownBy(() -> BaekjoonCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookieTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookieTest.java new file mode 100644 index 00000000..bc611d88 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonGlobalCookieTest.java @@ -0,0 +1,106 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.BaekjoonCookieErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BaekjoonGlobalCookieTest { + + @DisplayName("BaekjoonGlobalCookie를 생성할 때, globalUserId가 null이면 예외를 던진다.") + @Test + void validateGlobalUserIdWhenNull() { + // given + BaekjoonCookie 백준_쿠키 = BaekjoonCookie.builder() + .cookie("dummyCookie") + .nowDateTime(LocalDateTime.of(2021, 1, 1, 0, 0, 0)) + .build(); + + String 백준_아이디 = null; + String 리프레시_토큰 = "testRefreshToken"; + + + // when & then + assertThatThrownBy(() -> BaekjoonGlobalCookie.builder() + .baekjoonCookie(백준_쿠키) + .globalUserId(백준_아이디) + .baekjoonRefreshToken(리프레시_토큰) + .build()) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_GLOBAL_USER_ID.getMessage()); + } + + @DisplayName("BaekjoonGlobalCookie를 생성할 때, globalUserId가 빈 문자열이면 예외를 던진다.") + @Test + void validateGlobalUserIdWithEmptyString() { + // given + BaekjoonCookie 백준_쿠키 = BaekjoonCookie.builder() + .cookie("dummyCookie") + .nowDateTime(LocalDateTime.of(2021, 1, 1, 0, 0, 0)) + .build(); + + String 백준_아이디 = " "; + String 리프레시_토큰 = "testRefreshToken"; + + + // when & then + assertThatThrownBy(() -> BaekjoonGlobalCookie.builder() + .baekjoonCookie(백준_쿠키) + .globalUserId(백준_아이디) + .baekjoonRefreshToken(리프레시_토큰) + .build()) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_GLOBAL_USER_ID.getMessage()); + } + + @DisplayName("BaekjoonGlobalCookie를 생성할 때, refreshToken이 빈 문자열이면 예외를 던진다.") + @Test + void validateRefreshTokenWithEmptyString() { + // given + BaekjoonCookie 백준_쿠키 = BaekjoonCookie.builder() + .cookie("dummyCookie") + .nowDateTime(LocalDateTime.of(2021, 1, 1, 0, 0, 0)) + .build(); + + String 백준_아이디 = "testGlobalUserId"; + String 리프레시_토큰 = " "; + + + // when & then + assertThatThrownBy(() -> BaekjoonGlobalCookie.builder() + .baekjoonCookie(백준_쿠키) + .globalUserId(백준_아이디) + .baekjoonRefreshToken(리프레시_토큰) + .build()) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_BAEKJOON_REFRESH_TOKEN.getMessage()); + } + + @DisplayName("BaekjoonGlobalCookie를 생성할 때, refreshToken이 null이면 예외를 던진다.") + @Test + void validateRefreshTokenWithNull() { + // given + BaekjoonCookie 백준_쿠키 = BaekjoonCookie.builder() + .cookie("dummyCookie") + .nowDateTime(LocalDateTime.of(2021, 1, 1, 0, 0, 0)) + .build(); + + String 백준_아이디 = "testGlobalUserId"; + String 리프레시_토큰 = null; + + + // when & then + assertThatThrownBy(() -> BaekjoonGlobalCookie.builder() + .baekjoonCookie(백준_쿠키) + .globalUserId(백준_아이디) + .baekjoonRefreshToken(리프레시_토큰) + .build()) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_BAEKJOON_REFRESH_TOKEN.getMessage()); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookieTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookieTest.java new file mode 100644 index 00000000..a38cea0a --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/cookie/BaekjoonMemberCookieTest.java @@ -0,0 +1,237 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.judgement.domain.error.BaekjoonCookieErrorCode; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BaekjoonMemberCookieTest { + @DisplayName("백준 쿠키를 빈 값으로 업데이트 할 수 없다.") + @Test + void updateCookieWithEmptyValue() { + // given + String 쿠키 = "testCookie"; + final Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .cookie(쿠키) + .nowDateTime(등록된_시간) + .member(사용자) + .build(); + + String 새로운_쿠키 = " "; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + // when & then + assertThatThrownBy(() -> 백준_사용자_쿠키.updateCookie(새로운_쿠키, 다시_로그인_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + + } + + @DisplayName("백준 쿠키를 null 값으로 업데이트 할 수 없다.") + @Test + void updateCookieWithNullValue() { + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + String 새로운_쿠키 = null; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + // when & then + assertThatThrownBy(() -> 백준_사용자_쿠키.updateCookie(새로운_쿠키, 다시_로그인_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + } + + @DisplayName("백준 쿠키를 업데이트하는 시간이 null인 경우 업데이트 할 수 없다.") + @Test + void updateCookieWithNullNowDateTime() { + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + String 새로운_쿠키 = "newCookie"; + LocalDateTime 다시_로그인_시간 = null; + // when & then + assertThatThrownBy(() -> 백준_사용자_쿠키.updateCookie(새로운_쿠키, 다시_로그인_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.INVALID_COOKIE_VALUE.getMessage()); + } + + @DisplayName("백준 쿠키 상태를 로그아웃 상태로 변경할 수 있다.") + @Test + void setLoggedOut() { + // given + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + LocalDateTime 로그아웃_시간 = LocalDateTime.of(2021, 1, 1, 6, 0); + + // when + 백준_사용자_쿠키.setLoggedOut(로그아웃_시간); + + // then + assertThat(백준_사용자_쿠키) + .extracting("baekjoonCookie.cookieStatus", "baekjoonCookie.expiredAt") + .contains(CookieStatus.LOGGED_OUT, 로그아웃_시간); + + } + + @DisplayName("이미 로그아웃 상태인 백준 쿠키는 다시 로그아웃 상태로 변경할 수 없다.") + @Test + void setLoggedOutWhenAlreadyLoggedOut() { + // given + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + LocalDateTime 로그아웃_시간 = LocalDateTime.of(2021, 1, 1, 6, 0); + 백준_사용자_쿠키.setLoggedOut(로그아웃_시간); + + // when & then + assertThatThrownBy(() -> 백준_사용자_쿠키.setLoggedOut(로그아웃_시간)) + .isInstanceOf(MorandiException.class) + .hasMessage(BaekjoonCookieErrorCode.ALREADY_LOGGED_OUT.getMessage()); + } + + @DisplayName("백준 쿠키를 만료된 상태에서 유효한 상태로 변경할 수 있다.") + @Test + void updateCookieWhenExpired() { + // given + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + 백준_사용자_쿠키.setLoggedOut(LocalDateTime.of(2021, 1, 1, 6, 0)); + + String 새로운_쿠키 = "newCookie"; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + + // when + 백준_사용자_쿠키.updateCookie(새로운_쿠키, 다시_로그인_시간); + + // then + assertThat(백준_사용자_쿠키) + .extracting("baekjoonCookie.cookieStatus", "baekjoonCookie.expiredAt") + .contains(CookieStatus.LOGGED_IN, 다시_로그인_시간.plusHours(6)); + } + + @DisplayName("백준 쿠키를 만료된 상태에서 유효한 상태로 변경할 수 있다.") + @Test + void updateCookie() { + // given + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + + String 새로운_쿠키 = "newCookie"; + LocalDateTime 다시_로그인_시간 = LocalDateTime.of(2021, 1, 1, 3, 0); + + + // when + 백준_사용자_쿠키.updateCookie(새로운_쿠키, 다시_로그인_시간); + + // then + assertThat(백준_사용자_쿠키) + .extracting("baekjoonCookie.cookieStatus", "baekjoonCookie.expiredAt") + .contains(CookieStatus.LOGGED_IN, 다시_로그인_시간.plusHours(6)); + + } + + @DisplayName("백준 쿠키가 유효한지 확인할 수 있다.") + @Test + void validateCookie() { + // given + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + // when + boolean isValidCookie = 백준_사용자_쿠키.isValidCookie(LocalDateTime.of(2021, 1, 1, 3, 0)); + + // then + assertThat(isValidCookie).isTrue(); + + } + + @DisplayName("만료 시간이 지난 경우 백준 쿠키는 Valid하지 않다.") + @Test + void validateCookieWhenExpired() { + // given + String 쿠키 = "testCookie"; + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 등록된_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(등록된_시간) + .build(); + + LocalDateTime 만료된_시간 = LocalDateTime.of(2021, 1, 1, 6, 0); + + // when + final boolean validCookie = 백준_사용자_쿠키.isValidCookie(만료된_시간); + + // then + assertThat(validCookie).isFalse(); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResultTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResultTest.java new file mode 100644 index 00000000..51a3e9f9 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/BaekjoonJudgementResultTest.java @@ -0,0 +1,199 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.result; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +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.assertThatThrownBy; + +class BaekjoonJudgementResultTest { + + @DisplayName("BaekjoonCorrectInfo default 객체 생성 테스트") + @Test + void createDefaultCorrectInfo() { + // when + BaekjoonJudgementResult defaultResult = BaekjoonJudgementResult.defaultResult(); + + // then + assertThat(defaultResult) + .extracting("subtaskScore", "partialScore", "ac", "tot") + .contains(0, 0, 0, 0); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 subtaskScore null인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNullSubtaskScore() { + // given + Integer subtaskScore = null; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.subtaskScoreFrom(subtaskScore)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.SUBTASK_SCORE_IS_NULL.getMessage()); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 subtaskScore 음수인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNegativeSubtaskScore() { + // given + Integer subtaskScore = -1; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.subtaskScoreFrom(subtaskScore)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.SUBTASK_SCORE_IS_NEGATIVE.getMessage()); + } + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 partialScore가 null인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNullPartialScore() { + // given + Integer partialScore = null; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.partialScoreFrom(partialScore)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.PARTIAL_SCORE_IS_NULL.getMessage()); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 partialScore가 음수인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNegativePartialScore() { + // given + Integer partialScore = -1; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.partialScoreFrom(partialScore)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.PARTIAL_SCORE_IS_NEGATIVE.getMessage()); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 ac가 null인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNullAcTot() { + // given + Integer ac = null; + Integer tot = 10; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.acTotOf(ac, tot)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.AC_OR_TOT_IS_NULL.getMessage()); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 tot가 null인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNullTot() { + // given + Integer ac = 0; + Integer tot = null; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.acTotOf(ac, tot)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.AC_OR_TOT_IS_NULL.getMessage()); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 ac가 음수인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNegativeAc() { + // given + Integer ac = -1; + Integer tot = 10; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.acTotOf(ac, tot)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.AC_OR_TOT_IS_NEGATIVE.getMessage()); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 tot가 음수인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNegativeTot() { + // given + Integer ac = 0; + Integer tot = -1; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.acTotOf(ac, tot)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.AC_OR_TOT_IS_NEGATIVE.getMessage()); + } + + @DisplayName("BaekjoonCorrectInfo 객체 생성 시 ac가 tot보다 큰 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithAcGreaterThanTot() { + // given + Integer ac = 10; + Integer tot = 5; + + // when, then + assertThatThrownBy(() -> BaekjoonJudgementResult.acTotOf(ac, tot)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.AC_GREATER_THAN_TOT.getMessage()); + } + + @DisplayName("defaultResult로 생성한 BaekjoonCorrectInfo 객체의 subtaskScore, partialScore, ac, tot 값 확인 테스트") + @Test + void defaultResult() { + // given + + + // when + BaekjoonJudgementResult defaultResult = BaekjoonJudgementResult.defaultResult(); + + // then + assertThat(defaultResult) + .extracting("subtaskScore", "partialScore", "ac", "tot") + .contains(0, 0, 0, 0); + + } + + @DisplayName("subtaskScoreFrom으로 생성한 BaekjoonCorrectInfo 객체의 subtaskScore 값 확인 테스트") + @Test + void subtaskScoreFrom() { + // given + Integer subtaskScore = 10; + + // when + BaekjoonJudgementResult subtaskScoreResult = BaekjoonJudgementResult.subtaskScoreFrom(subtaskScore); + + // then + assertThat(subtaskScoreResult) + .extracting("subtaskScore") + .isEqualTo(10); + } + + @DisplayName("partialScoreFrom으로 생성한 BaekjoonCorrectInfo 객체의 partialScore 값 확인 테스트") + @Test + void partialScoreFrom() { + // given + Integer partialScore = 10; + + // when + BaekjoonJudgementResult partialScoreResult = BaekjoonJudgementResult.partialScoreFrom(partialScore); + + // then + assertThat(partialScoreResult) + .extracting("partialScore") + .isEqualTo(10); + } + + @DisplayName("acTotOf으로 생성한 BaekjoonCorrectInfo 객체의 ac, tot 값 확인 테스트") + @Test + void acTotOf() { + // given + Integer ac = 10; + Integer tot = 20; + + // when + BaekjoonJudgementResult acTotResult = BaekjoonJudgementResult.acTotOf(ac, tot); + + // then + assertThat(acTotResult) + .extracting("ac", "tot") + .contains(10, 20); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/JudgementResultTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/JudgementResultTest.java new file mode 100644 index 00000000..b2005c06 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/result/JudgementResultTest.java @@ -0,0 +1,175 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.result; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementResult; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementStatus; +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.assertThatThrownBy; + +class JudgementResultTest { + + @DisplayName("JudgementResult reject 정적 팩토리 메서드 테스트") + @Test + void rejected() { + // given + JudgementStatus judgementStatus = JudgementStatus.WRONG_ANSWER; + + // when + final JudgementResult judgementResult = JudgementResult.rejected(judgementStatus); + + // then + assertThat(judgementResult).isNotNull() + .extracting("judgementStatus", "memory", "time") + .containsExactly(judgementStatus, 0, 0); + } + + @DisplayName("JudgementResult 생성자에서 JudgementStatus가 null인 경우 예외 발생 테스트") + @Test + void constructorWithNullJudgementStatus() { + // given + + // when & then + assertThatThrownBy(() -> JudgementResult.builder() + .judgementStatus(null) + .memory(0) + .time(0) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.JUDGEMENT_RESULT_NOT_FOUND.getMessage()); + } + + @DisplayName("JudgementStatus가 ACCEPTED가 아닐 때 메모리 값이 0이 아닌 경우 예외 발생 테스트") + @Test + void validateCanExistMemory() { + // given + Integer memory = 1; + Integer time = 0; + + // when & then + assertThatThrownBy(() -> JudgementResult.builder() + .judgementStatus(JudgementStatus.WRONG_ANSWER) + .memory(memory) + .time(time) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.NOT_ACCEPTED_CANNOT_HAVE_MEMORY_AND_TIME.getMessage()); + } + + @DisplayName("JudgementStatus가 ACCEPTED가 아닐 때 시간 값이 0이 아닌 경우 예외 발생 테스트") + @Test + void validateCanExistTime() { + // given + Integer memory = 0; + Integer time = 1; + + // when & then + assertThatThrownBy(() -> JudgementResult.builder() + .judgementStatus(JudgementStatus.WRONG_ANSWER) + .memory(memory) + .time(time) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.NOT_ACCEPTED_CANNOT_HAVE_MEMORY_AND_TIME.getMessage()); + } + + @DisplayName("정상적인 JudgementResult 객체 생성 테스트") + @Test + void createCorrectInfo() { + // given + Integer memory = 0; + Integer time = 0; + + // when + final JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(JudgementStatus.ACCEPTED) + .memory(memory) + .time(time) + .build(); + + // then + assertThat(judgementResult).isNotNull() + .extracting("memory", "time") + .containsExactly(memory, time); + + } + + @DisplayName("JudgementResult 객체 생성 시 memory가 null인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNullMemory() { + // given + Integer memory = null; + Integer time = 0; + + // when, then + assertThatThrownBy(() -> JudgementResult.builder() + .judgementStatus(JudgementStatus.ACCEPTED) + .memory(memory) + .time(time) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.MEMORY_IS_NULL.getMessage()); + } + + @DisplayName("JudgementResult 객체 생성 시 memory가 음수인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNegativeMemory() { + // given + Integer memory = -1; + Integer time = 0; + + // when, then + assertThatThrownBy(() -> JudgementResult.builder() + .judgementStatus(JudgementStatus.ACCEPTED) + .memory(memory) + .time(time) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.MEMORY_IS_NEGATIVE.getMessage()); + } + + @DisplayName("JudgementResult 객체 생성 시 time이 null인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNullTime() { + // given + Integer memory = 0; + Integer time = null; + + // when, then + assertThatThrownBy(() -> JudgementResult.builder() + .judgementStatus(JudgementStatus.ACCEPTED) + .memory(memory) + .time(time) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.TIME_IS_NULL.getMessage()); + } + + @DisplayName("JudgementResult 객체 생성 시 time이 음수인 경우 예외 발생 테스트") + @Test + void createCorrectInfoWithNegativeTime() { + // given + Integer memory = 0; + Integer time = -1; + + // when, then + assertThatThrownBy(() -> JudgementResult.builder() + .judgementStatus(JudgementStatus.ACCEPTED) + .memory(memory) + .time(time) + .build() + ) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.TIME_IS_NEGATIVE.getMessage()); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmitTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmitTest.java new file mode 100644 index 00000000..d570f6ea --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/baekjoon/submit/BaekjoonSubmitTest.java @@ -0,0 +1,117 @@ +package kr.co.morandi.backend.judgement.domain.model.baekjoon.submit; + +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.factory.TestDefenseFactory; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.factory.TestProblemFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonJudgementResult; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementResult; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.Map; + +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; +import static kr.co.morandi.backend.judgement.domain.model.submit.JudgementStatus.ACCEPTED; +import static org.assertj.core.api.Assertions.assertThat; + +@ActiveProfiles("test") +class BaekjoonSubmitTest { + + @DisplayName("백준 제출을 생성할 수 있다.") + @Test + void create() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + // when + BaekjoonSubmit 백준_제출 = BaekjoonSubmit.builder() + .sourceCode(제출할_코드) + .member(사용자) + .submitDateTime(제출_시간) + .detail(dailyRecord.getDetail(1L)) + .submitVisibility(SubmitVisibility.OPEN) + .build(); + + // then + assertThat(백준_제출) + .isNotNull() + .extracting("sourceCode.sourceCode", "sourceCode.language", "member", "detail", "submitVisibility") + .contains(제출할_코드.getSourceCode(), 제출할_코드.getLanguage(), 사용자, dailyRecord.getDetail(1L), SubmitVisibility.OPEN); + } + + @DisplayName("백준 제출을 default와 함께 정답 처리할 수 있다.") + @Test + void updateStatusToAccepted() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + BaekjoonSubmit 백준_제출 = BaekjoonSubmit.builder() + .sourceCode(제출할_코드) + .member(사용자) + .submitDateTime(제출_시간) + .detail(dailyRecord.getDetail(1L)) + .submitVisibility(SubmitVisibility.OPEN) + .build(); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(ACCEPTED) + .memory(300) + .time(30) + .build(); + + + // when + final BaekjoonJudgementResult baekjoonJudgementResult = BaekjoonJudgementResult.defaultResult(); + + 백준_제출.updateJudgementResult(judgementResult, baekjoonJudgementResult); + + // then + assertThat(백준_제출) + .extracting("judgementResult.judgementStatus", "judgementResult.memory", "judgementResult.time", "baekjoonJudgementResult") + .contains(ACCEPTED, 300, 30, baekjoonJudgementResult); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCodeTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCodeTest.java new file mode 100644 index 00000000..5fb16680 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SourceCodeTest.java @@ -0,0 +1,57 @@ +package kr.co.morandi.backend.judgement.domain.model.submit; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +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.assertThatThrownBy; + +class SourceCodeTest { + + @DisplayName("SourceCode 객체를 생성할 수 있다.") + @Test + void sourceCodeOf() { + // given + String source = "sourceCode"; + + // when + SourceCode sourceCode = SourceCode.of(source, Language.JAVA); + + // then + assertThat(sourceCode) + .isNotNull() + .extracting("sourceCode", "language") + .containsExactly(source, Language.JAVA); + + } + + @DisplayName("SourceCode 객체를 생성할 때 source가 null이면 예외가 발생한다.") + @Test + void sourceCodeOfWithNullSource() { + // given + String source = null; + + // when & then + assertThatThrownBy(() -> SourceCode.of(source, Language.JAVA)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.SOURCE_CODE_NOT_FOUND.getMessage()); + } + + @DisplayName("SourceCode 객체를 생성할 때 source가 빈 문자열이면 예외가 발생한다.") + @Test + void sourceCodeOfWithEmptySource() { + // given + String source = ""; + + // when & then + assertThatThrownBy(() -> SourceCode.of(source, Language.JAVA)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.SOURCE_CODE_NOT_FOUND.getMessage()); + } + + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitTest.java new file mode 100644 index 00000000..7703e99c --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/model/submit/SubmitTest.java @@ -0,0 +1,578 @@ +package kr.co.morandi.backend.judgement.domain.model.submit; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.domain.model.record.Detail; +import kr.co.morandi.backend.factory.TestDefenseFactory; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.factory.TestProblemFactory; +import kr.co.morandi.backend.judgement.domain.error.JudgementResultErrorCode; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.Map; + +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; +import static kr.co.morandi.backend.judgement.domain.model.submit.JudgementStatus.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@ActiveProfiles("test") +class SubmitTest { + + /* + * 추상 클래스에 관한 공통 메서드 테스트 작성을 위해 + * Submit 클래스를 상속받는 SubmitImpl 클래스를 생성했습니다. + */ + static class SubmitTestImpl extends Submit { + public SubmitTestImpl(Member member, Detail detail, SourceCode sourceCode, LocalDateTime submitDateTime, SubmitVisibility submitVisibility) { + super(member, detail, sourceCode, submitDateTime, submitVisibility); + } + } + + @DisplayName("Submit을 생성할 수 있다.") + @Test + void createSubmit() { + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + // when + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + // then + assertThat(제출) + .extracting("member", "detail", "sourceCode", "submitVisibility", "submitDateTime") + .contains(사용자, dailyRecord.getDetail(1L), 제출할_코드, SubmitVisibility.OPEN, 제출_시간); + + } + + @DisplayName("sourceCode를 추가하지 않고 Submit을 생성하려고 하면 예외가 발생한다.") + @Test + void createSubmitWithoutDetail() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + // when & then + assertThatThrownBy( + () -> new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + null, + 제출_시간, + SubmitVisibility.OPEN) + ).isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.SOURCE_CODE_IS_NULL.getMessage()); + + + } + + @DisplayName("detail을 추가하지 않고 Submit을 생성하려고 하면 예외가 발생한다.") + @Test + void createSubmitWithoutSourceCode() { + // given + Member 사용자 = TestMemberFactory.createMember(); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + // when & then + assertThatThrownBy( + () -> new SubmitTestImpl(사용자, + null, + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN) + ).isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.DETAIL_IS_NULL.getMessage()); + + } + + @DisplayName("submitVisibility를 추가하지 않고 Submit을 생성하려고 하면 예외가 발생한다.") + @Test + void createSubmitWithoutSubmitVisibility() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + // when & then + assertThatThrownBy( + () -> new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + null) + ).isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.VISIBILITY_NOT_NULL.getMessage()); + + + } + + + @DisplayName("submit을 여러 번 진행하면 detail의 submitCount가 1씩 증가한다.") + @Test + void 여러_번_제출시_Detail의_제출횟수가_증가한다() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + // when & then + Submit 제출 = new SubmitTestImpl(사용자, dailyRecord.getDetail(1L), 제출할_코드, 제출_시간, SubmitVisibility.OPEN); + assertThat(제출.getDetail().getSubmitCount()).isEqualTo(1L); + + new SubmitTestImpl(사용자, dailyRecord.getDetail(1L), 제출할_코드, 제출_시간, SubmitVisibility.OPEN); + assertThat(제출.getDetail().getSubmitCount()).isEqualTo(2L); + + new SubmitTestImpl(사용자, dailyRecord.getDetail(1L), 제출할_코드, 제출_시간, SubmitVisibility.OPEN); + assertThat(제출.getDetail().getSubmitCount()).isEqualTo(3L); + + } + + @DisplayName("submit을 진행하면 detail의 submitCount가 1 증가한다.") + @Test + void 제출시_Detail의_제출횟수가_증가한다() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + // when + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + // then + assertThat(제출) + .extracting("detail.submitCount") + .isEqualTo(1L); + + } + + @DisplayName("제출 시간이 null일 때 Submit을 생성하려고 하면 예외가 발생한다.") + @Test + void submitDateTimeIsNull() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = null; + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + // when & then + assertThatThrownBy( + () -> new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN) + ).isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.SUBMIT_DATE_TIME_IS_NULL.getMessage()); + + } + + @DisplayName("Submit 후 정답 상태로 변경할 수 있다.") + @Test + void updateStatusToAccepted() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(ACCEPTED) + .memory(300) + .time(30) + .build(); + // when + 제출.updateJudgementResult(judgementResult); + + // then + assertThat(제출) + .extracting("judgementResult.judgementStatus", "judgementResult.memory", "judgementResult.time") + .contains(ACCEPTED, 300, 30); + + } + + @DisplayName("이미 정답상태인 Submit은 다시 정답상태로 변경할 수 없다.") + @Test + void updateStatusToAcceptedWhenAlreadyAccepted() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(ACCEPTED) + .memory(300) + .time(30) + .build(); + + 제출.updateJudgementResult(judgementResult); + + // when & then + assertThatThrownBy(() -> 제출.updateJudgementResult(judgementResult)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.ALREADY_JUDGED.getMessage()); + + } + + @DisplayName("이미 런타임 에러인 Submit은 다시 다른 상태로 변경할 수 없다.") + @Test + void updateStatusToAcceptedWhenAlreadyRuntimeError() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(RUNTIME_ERROR) + .memory(0) + .time(0) + .build(); + + 제출.updateJudgementResult(judgementResult); + + // when & then + assertThatThrownBy(() -> 제출.updateJudgementResult(judgementResult)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.ALREADY_JUDGED.getMessage()); + + } + + @DisplayName("이미 컴파일 에러인 Submit은 다시 다른 상태로 변경할 수 없다.") + @Test + void updateStatusToAcceptedWhenAlreadyCompileError() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(COMPILE_ERROR) + .memory(0) + .time(0) + .build(); + + 제출.updateJudgementResult(judgementResult); + + // when & then + assertThatThrownBy(() -> 제출.updateJudgementResult(judgementResult)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.ALREADY_JUDGED.getMessage()); + + } + + @DisplayName("이미 시간 초과인 Submit은 다시 다른 상태로 변경할 수 없다.") + @Test + void updateStatusToAcceptedWhenAlreadyTimeLimitExceeded() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(TIME_LIMIT_EXCEEDED) + .memory(0) + .time(0) + .build(); + + 제출.updateJudgementResult(judgementResult); + + // when & then + assertThatThrownBy(() -> 제출.updateJudgementResult(judgementResult)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.ALREADY_JUDGED.getMessage()); + + } + + @DisplayName("이미 메모리 초과인 Submit은 다시 다른 상태로 변경할 수 없다.") + @Test + void updateStatusToAcceptedWhenAlreadyMemoryLimitExceeded() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(MEMORY_LIMIT_EXCEEDED) + .memory(0) + .time(0) + .build(); + + 제출.updateJudgementResult(judgementResult); + + // when & then + assertThatThrownBy(() -> 제출.updateJudgementResult(judgementResult)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.ALREADY_JUDGED.getMessage()); + + } + + @DisplayName("이미 틀린 답인 Submit은 다시 다른 상태로 변경할 수 없다.") + @Test + void updateStatusToAcceptedWhenAlreadyWrongAnswer() { + // given + Member 사용자 = TestMemberFactory.createMember(); + Map 문제 = TestProblemFactory.createProblems(5); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + Submit 제출 = new SubmitTestImpl(사용자, + dailyRecord.getDetail(1L), + 제출할_코드, + 제출_시간, + SubmitVisibility.OPEN); + + JudgementResult judgementResult = JudgementResult.builder() + .judgementStatus(WRONG_ANSWER) + .memory(0) + .time(0) + .build(); + + 제출.updateJudgementResult(judgementResult); + + // when & then + assertThatThrownBy(() -> 제출.updateJudgementResult(judgementResult)) + .isInstanceOf(MorandiException.class) + .hasMessage(JudgementResultErrorCode.ALREADY_JUDGED.getMessage()); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementServiceTest.java b/src/test/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementServiceTest.java new file mode 100644 index 00000000..db19548e --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/domain/service/BaekjoonJudgementServiceTest.java @@ -0,0 +1,111 @@ +package kr.co.morandi.backend.judgement.domain.service; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.factory.TestDefenseFactory; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.factory.TestProblemFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.result.BaekjoonJudgementResult; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.domain.model.submit.JudgementStatus; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.judgement.infrastructure.persistence.submit.BaekjoonSubmitRepository; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.AopTestUtils; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; + +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class BaekjoonJudgementServiceTest extends IntegrationTestSupport { + + @Autowired + private BaekjoonSubmitRepository baekjoonSubmitRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @Autowired + private BaekjoonJudgementService baekjoonJudgementService; + + @DisplayName("채점 결과를 업데이트할 수 있다.") + @Test + void canUpdateJudgementStatusCorrectly() { + + baekjoonJudgementService = AopTestUtils.getTargetObject(baekjoonJudgementService); + + Member 사용자 = TestMemberFactory.createMember(); + memberRepository.save(사용자); + Map 문제 = TestProblemFactory.createProblems(5); + problemRepository.saveAll(문제.values()); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + dailyDefenseRepository.save(오늘의_문제); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + dailyRecordRepository.save(dailyRecord); + + LocalDateTime 제출_시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + BaekjoonSubmit 백준_제출 = BaekjoonSubmit.builder() + .sourceCode(제출할_코드) + .member(사용자) + .submitDateTime(제출_시간) + .detail(dailyRecord.getDetail(1L)) + .submitVisibility(SubmitVisibility.OPEN) + .build(); + + final BaekjoonSubmit 저장된_백준_제출 = baekjoonSubmitRepository.save(백준_제출); + + final BaekjoonJudgementResult 백준_채점_디테일_정보 = BaekjoonJudgementResult.defaultResult(); + final JudgementStatus 채점_결과 = JudgementStatus.ACCEPTED; + + // when + baekjoonJudgementService.asyncUpdateJudgementStatus(저장된_백준_제출.getSubmitId(), 채점_결과, 512, 120, 백준_채점_디테일_정보); + + final Optional maybeBaekjoonSubmit = baekjoonSubmitRepository.findById(저장된_백준_제출.getSubmitId()); + + assertThat(maybeBaekjoonSubmit).isPresent() + .get() + .isNotNull() + .extracting("judgementResult.memory", "judgementResult.time", "baekjoonJudgementResult.subtaskScore", "baekjoonJudgementResult.partialScore", "baekjoonJudgementResult.ac", "baekjoonJudgementResult.tot") + .containsExactly(512, 120, 백준_채점_디테일_정보.getSubtaskScore(), 백준_채점_디테일_정보.getPartialScore(), 백준_채점_디테일_정보.getAc(), 백준_채점_디테일_정보.getTot()); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/SubmitVisibilityTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/SubmitVisibilityTest.java new file mode 100644 index 00000000..5b0c572f --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/SubmitVisibilityTest.java @@ -0,0 +1,121 @@ +package kr.co.morandi.backend.judgement.infrastructure; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +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.assertThatThrownBy; + +class SubmitVisibilityTest { + + @DisplayName("getValue 테스트") + @Test + void getValue() { + // given + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + + // when + String value = submitVisibility.getValue(); + + // then + assertThat(value).isEqualTo("OPEN"); + } + @DisplayName("OPEN 값으로 SubmitVisibility 생성") + @Test + void fromOPEN() { + // given + String value = "OPEN"; + + // when + SubmitVisibility submitVisibility = SubmitVisibility.fromValue(value); + + // then + assertThat(submitVisibility).isEqualTo(SubmitVisibility.OPEN); + + } + @DisplayName("대소문자 구분없이 값으로 SubmitVisibility 생성") + @Test + void fromOpen() { + // given + String value = "Open"; + + // when + SubmitVisibility submitVisibility = SubmitVisibility.fromValue(value); + + // then + assertThat(submitVisibility).isEqualTo(SubmitVisibility.OPEN); + + } + + @DisplayName("CLOSE 값으로 SubmitVisibility 생성") + @Test + void fromCLOSE() { + // given + String value = "CLOSE"; + + // when + SubmitVisibility submitVisibility = SubmitVisibility.fromValue(value); + + // then + assertThat(submitVisibility).isEqualTo(SubmitVisibility.CLOSE); + + } + @DisplayName("대소문자 구분없이 값으로 SubmitVisibility 생성") + @Test + void fromClose() { + // given + String value = "Close"; + + // when + SubmitVisibility submitVisibility = SubmitVisibility.fromValue(value); + + // then + assertThat(submitVisibility).isEqualTo(SubmitVisibility.CLOSE); + + } + + @DisplayName("null 값으로 SubmitVisibility 생성") + @Test + void fromNull() { + // given + String value = null; + + // when & then + assertThatThrownBy(() -> SubmitVisibility.fromValue(value)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.SUBMIT_VISIBILITY_NOT_FOUND.getMessage()); + + } + + @DisplayName("빈 값으로 SubmitVisibility 생성") + @Test + void fromEmpty() { + // given + String value = ""; + + // when & then + assertThatThrownBy(() -> SubmitVisibility.fromValue(value)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.SUBMIT_VISIBILITY_NOT_FOUND.getMessage()); + + } + + @DisplayName("잘못된 값으로 SubmitVisibility 생성") + @Test + void fromInvalidValue() { + // given + String value = "INVALID"; + + // when & then + assertThatThrownBy(() -> SubmitVisibility.fromValue(value)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.INVALID_VISIBILITY_VALUE.getMessage()); + + } + + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParserTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParserTest.java new file mode 100644 index 00000000..279d2cee --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitHtmlParserTest.java @@ -0,0 +1,156 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.*; + +class BaekjoonSubmitHtmlParserTest { + + @DisplayName("CSRF 키를 정상적으로 파싱한다.") + @Test + void parseCsrfKeyInSubmitPage_validResponse() { + // given + String validHtml = ""; + + // when + String csrfKey = BaekjoonSubmitHtmlParser.parseCsrfKeyInSubmitPage(validHtml); + + // then + assertThat(csrfKey).isEqualTo("validCsrfKey"); + } + + @DisplayName("CSRF 키가 없는 경우 예외를 던진다.") + @Test + void parseCsrfKeyInSubmitPage_missingCsrfKey() { + // given + String invalidHtml = ""; + + // when & then + assertThatThrownBy(() -> BaekjoonSubmitHtmlParser.parseCsrfKeyInSubmitPage(invalidHtml)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.CSRF_KEY_NOT_FOUND.getMessage()); + } + + @DisplayName("null 응답인 경우 예외를 던진다.") + @Test + void parseCsrfKeyInSubmitPage_nullResponse() { + // given + String nullResponse = null; + + // when & then + assertThatThrownBy(() -> BaekjoonSubmitHtmlParser.parseCsrfKeyInSubmitPage(nullResponse)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.BAEKJOON_SUBMIT_PAGE_ERROR.getMessage()); + } + + @DisplayName("솔루션 아이디를 정상적으로 파싱한다.") + @Test + void parseSolutionIdFromHtml_validHtml() { + // given + String validHtml = """ + + + + + + +
123456
+ """; + + // when + String solutionId = BaekjoonSubmitHtmlParser.parseSolutionIdFromHtml(validHtml); + + // then + assertThat(solutionId).isEqualTo("123456"); + } + + @DisplayName("솔루션 아이디가 없는 경우 예외를 던진다.") + @Test + void parseSolutionIdFromHtml_missingSolutionId() { + // given + String invalidHtml = """ + + + + + + +
+ """; + + // when & then + assertThatThrownBy(() -> BaekjoonSubmitHtmlParser.parseSolutionIdFromHtml(invalidHtml)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.CANT_FIND_SOLUTION_ID.getMessage()); + } + + @DisplayName("상태 테이블이 없는 경우 예외를 던진다.") + @Test + void parseSolutionIdFromHtml_missingStatusTable() { + // given + String invalidHtml = ""; + + // when & then + assertThatThrownBy(() -> BaekjoonSubmitHtmlParser.parseSolutionIdFromHtml(invalidHtml)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.CANT_FIND_SOLUTION_ID.getMessage()); + } + + @DisplayName("첫 번째 행이 없는 경우 예외를 던진다.") + @Test + void parseSolutionIdFromHtml_missingFirstRow() { + // given + String invalidHtml = """ + + + +
+ """; + + // when & then + assertThatThrownBy(() -> BaekjoonSubmitHtmlParser.parseSolutionIdFromHtml(invalidHtml)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.CANT_FIND_SOLUTION_ID.getMessage()); + } + + @DisplayName("solutionIdElement가 null인 경우 예외를 던진다.") + @Test + void parseSolutionIdFromHtml_nullSolutionIdElement() { + // given + String invalidHtml = """ + + + + + +
+ """; + + // when & then + assertThatThrownBy(() -> BaekjoonSubmitHtmlParser.parseSolutionIdFromHtml(invalidHtml)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.CANT_FIND_SOLUTION_ID.getMessage()); + } + + @DisplayName("solutionIdElement의 텍스트가 비어있는 경우 예외를 던진다.") + @Test + void parseSolutionIdFromHtml_emptySolutionIdElement() { + // given + String invalidHtml = """ + + + + + + +
+ """; + + // when & then + assertThatThrownBy(() -> BaekjoonSubmitHtmlParser.parseSolutionIdFromHtml(invalidHtml)) + .isInstanceOf(MorandiException.class) + .hasMessage(SubmitErrorCode.CANT_FIND_SOLUTION_ID.getMessage()); + } +} diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCodeTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCodeTest.java new file mode 100644 index 00000000..95299331 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitLanguageCodeTest.java @@ -0,0 +1,58 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class BaekjoonSubmitLanguageCodeTest { + + @DisplayName("BaekjoonJudgementConstants의 CPP에 해당하는 아이디를 가져올 수 있다.") + @Test + void getLanguageId() { + // given + Language language = Language.CPP; + + // when + String languageId = BaekjoonSubmitLanguageCode.getLanguageCode(language); + + // then + assertThat(languageId) + .isEqualTo("84"); + + } + + @DisplayName("BaekjoonJudgementConstants의 JAVA 해당하는 아이디를 가져올 수 있다.") + @Test + void getLanguageId2() { + // given + Language language = Language.JAVA; + + // when + String languageId = BaekjoonSubmitLanguageCode.getLanguageCode(language); + + // then + assertThat(languageId) + .isEqualTo("93"); + + } + + @DisplayName("BaekjoonJudgementConstants의 PYTHON에 해당하는 아이디를 가져올 수 있다.") + @Test + void getLanguageId3() { + // given + Language language = Language.PYTHON; + + // when + String languageId = BaekjoonSubmitLanguageCode.getLanguageCode(language); + + // then + assertThat(languageId) + .isEqualTo("28"); + + } + + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitStrategyTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitStrategyTest.java new file mode 100644 index 00000000..a42f2a88 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/baekjoon/submit/BaekjoonSubmitStrategyTest.java @@ -0,0 +1,222 @@ +package kr.co.morandi.backend.judgement.infrastructure.baekjoon.submit; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.domain.error.SubmitErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.function.client.ClientRequest; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFunction; +import reactor.core.publisher.Mono; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class BaekjoonSubmitStrategyTest extends IntegrationTestSupport { + + @Autowired + private BaekjoonSubmitApiAdapter baekjoonSubmitApiAdapter; + + @Autowired + private ExchangeFunction exchangeFunction; + + @DisplayName("제출을 하고 솔루션 아이디를 가져온다.") + @Order(1) + @Test + void submitAndGetSolutionId() { + // given + // reqeust parameter + String 백준_문제_ID = "1000"; + String 사용자_쿠키 = "cookieValue"; + Language 제출_언어 = Language.PYTHON; + String 제출_코드_공개범위 = "open"; + String 제출_코드 = "print('Hello World')"; + + // WebClient response stubbing + String csrfKey = "stubbingCsrfKey"; + String csrfHtml = ""; + String solutionIdHtml = """ + + + + + + +
123456
+ """; + + // WebClient stubbing + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "text/html") + .body(csrfHtml) + .build())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND) + .header(HttpHeaders.LOCATION, "/status") + .build())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "text/html") + .body(solutionIdHtml) + .build())); + + // when + String solutionId = baekjoonSubmitApiAdapter.submitAndGetSolutionId(백준_문제_ID, 사용자_쿠키, 제출_언어, 제출_코드, 제출_코드_공개범위); + + // then + assertThat(solutionId) + .isNotNull() + .isEqualTo("123456"); + + } + + @DisplayName("제출할 때 CSRF_KEY를 가져오는데 HTML이 빈 문자열인 경우") + @Test + void CSRF_KEY를_가져오는데_HTML이_빈_문자열인_경우() { + // given + // reqeust parameter + String 백준_문제_ID = "1000"; + String 사용자_쿠키 = "cookieValue"; + Language 제출_언어 = Language.PYTHON; + String 제출_코드_공개범위 = "open"; + String 제출_코드 = "print('Hello World')"; + + // WebClient response stubbing + + // invalid csrf key + String csrfHtml = ""; + + + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "text/html") + .body(csrfHtml) + .build())); + + // when & then + assertThatThrownBy(() -> baekjoonSubmitApiAdapter.submitAndGetSolutionId(백준_문제_ID, 사용자_쿠키, 제출_언어, 제출_코드, 제출_코드_공개범위)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining("제출 페이지에서 CSRF 키를 찾을 수 없습니다."); + + } + + @DisplayName("제출할 때 CSRF_KEY를 가져오는데 실패한 경우") + @Test + void CSRF_KEY를_가져오는데_실패한_경우() { + // given + // reqeust parameter + String 백준_문제_ID = "1000"; + String 사용자_쿠키 = "cookieValue"; + Language 제출_언어 = Language.PYTHON; + String 제출_코드_공개범위 = "open"; + String 제출_코드 = "print('Hello World')"; + + // WebClient response stubbing + + // invalid csrf key + String csrfHtml = ""; + + + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "text/html") + .body(csrfHtml) + .build())); + + // when & then + assertThatThrownBy(() -> baekjoonSubmitApiAdapter.submitAndGetSolutionId(백준_문제_ID, 사용자_쿠키, 제출_언어, 제출_코드, 제출_코드_공개범위)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining("제출 페이지에서 CSRF 키를 찾을 수 없습니다."); + + } + + @DisplayName("제출을 한 뒤 Solution Id를 정상적으로 찾지 못한 경우") + @Test + void 제출_뒤_Solution_Id를_정상적으로_찾지_못한_경우() { + // given + // reqeust parameter + String 백준_문제_ID = "1000"; + String 사용자_쿠키 = "cookieValue"; + Language 제출_언어 = Language.PYTHON; + String 제출_코드_공개범위 = "open"; + String 제출_코드 = "print('Hello World')"; + + // WebClient response stubbing + String csrfKey = "stubbingCsrfKey"; + String csrfHtml = ""; + + // invalid solution id HTML + String solutionIdHtml = """ + + + + + + +
123456
+ """; + + // WebClient stubbing + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "text/html") + .body(csrfHtml) + .build())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND) + .header(HttpHeaders.LOCATION, "/status") + .build())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "text/html") + .body(solutionIdHtml) + .build())); + + // when & then + assertThatThrownBy(() -> baekjoonSubmitApiAdapter.submitAndGetSolutionId(백준_문제_ID, 사용자_쿠키, 제출_언어, 제출_코드, 제출_코드_공개범위)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining(SubmitErrorCode.CANT_FIND_SOLUTION_ID.getMessage()); + + } + + + @DisplayName("Redirection에서 location 헤더가 없는 경우 예외를 던진다.") + @Test + void handleRedirection_noLocationHeader() { + // given + // reqeust parameter + String 백준_문제_ID = "1000"; + String 사용자_쿠키 = "cookieValue"; + Language 제출_언어 = Language.PYTHON; + String 제출_코드_공개범위 = "open"; + String 제출_코드 = "print('Hello World')"; + + // WebClient response stubbing + String csrfKey = "stubbingCsrfKey"; + String csrfHtml = ""; + + when(exchangeFunction.exchange(any(ClientRequest.class))) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.OK) + .header("Content-Type", "text/html") + .body(csrfHtml) + .build())) + .thenReturn(Mono.just(ClientResponse.create(HttpStatus.FOUND) + .header("Content-Type", "text/html") + .build())); + + + // when & then + assertThatThrownBy(() -> baekjoonSubmitApiAdapter.submitAndGetSolutionId(백준_문제_ID, 사용자_쿠키, 제출_언어, 제출_코드, 제출_코드_공개범위)) + .isInstanceOf(MorandiException.class) + .hasMessageContaining(SubmitErrorCode.REDIRECTION_LOCATION_NOT_FOUND.getMessage()); + + } + + + + } diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitControllerTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitControllerTest.java new file mode 100644 index 00000000..1962a844 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/BaekjoonSubmitControllerTest.java @@ -0,0 +1,307 @@ +package kr.co.morandi.backend.judgement.infrastructure.controller; + +import kr.co.morandi.backend.ControllerTestSupport; +import kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.judgement.infrastructure.controller.request.BaekjoonJudgementRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.web.servlet.ResultActions; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class BaekjoonSubmitControllerTest extends ControllerTestSupport { + + @DisplayName("제출 요청을 받아 처리한다.") + @Test + void submit() throws Exception { + + Long defenseSessionId = 1L; + Long problemNumber = 1L; + Language language = Language.JAVA; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isOk()); + } + + @DisplayName("defenseSessionId가 null일 경우 처리한다.") + @Test + void submitWhenDefenseSessionIdIsNull() throws Exception { + + Long defenseSessionId = null; + Long problemNumber = 1L; + Language language = Language.JAVA; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("defenseSessionId: defenseSessionId가 존재해야 합니다.")); + + } + + @DisplayName("defenseSessionId가 음수일 경우 처리한다.") + @Test + void submitWhenDefenseSessionIdIsNegative() throws Exception { + + Long defenseSessionId = -1L; + Long problemNumber = 1L; + Language language = Language.JAVA; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("defenseSessionId: defenseSessionId는 양수여야 합니다.")); + + } + + @DisplayName("problemNumber가 null일 경우 처리한다.") + @Test + void submitWhenProblemNumberIsNull() throws Exception { + // when + Long defenseSessionId = 1L; + Long problemNumber = null; + Language language = Language.JAVA; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("problemNumber: problemNumber가 존재해야 합니다.")); + + } + + @DisplayName("problemNumber가 음수일 경우 처리한다.") + @Test + void submitWhenProblemNumberIsNegative() throws Exception { + // when + Long defenseSessionId = 1L; + Long problemNumber = -1L; + Language language = Language.JAVA; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("problemNumber: problemNumber가 양수여야 합니다.")); + + } + + + @DisplayName("language가 null일 경우 처리한다.") + @Test + void submitWhenLanguageIsNull() throws Exception { + // when + Long defenseSessionId = 1L; + Long problemNumber = 1L; + Language language = null; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("language: language가 존재해야 합니다.")); + + } + + @DisplayName("sourceCode가 null일 경우 처리한다.") + @Test + void submitWhenSourceCodeIsNull() throws Exception { + // when + Long defenseSessionId = 1L; + Long problemNumber = 1L; + Language language = Language.JAVA; + String sourceCode = null; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("sourceCode: sourceCode가 존재해야 합니다.")); + + } + + @DisplayName("sourceCode가 비어있을 경우 처리한다.") + @Test + void submitWhenSourceCodeIsEmpty() throws Exception { + // when + Long defenseSessionId = 1L; + Long problemNumber = 1L; + Language language = Language.JAVA; + String sourceCode = ""; + SubmitVisibility submitVisibility = SubmitVisibility.OPEN; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("sourceCode: sourceCode가 존재해야 합니다.")); + + } + + @DisplayName("submitVisibility가 null일 경우 처리한다.") + @Test + void submitWhenSubmitVisibilityIsNull() throws Exception { + // when + Long defenseSessionId = 1L; + Long problemNumber = 1L; + Language language = Language.JAVA; + String sourceCode = "sourceCode"; + SubmitVisibility submitVisibility = null; + BaekjoonJudgementRequest request = BaekjoonJudgementRequest.builder() + .defenseSessionId(defenseSessionId) + .problemNumber(problemNumber) + .language(language) + .sourceCode(sourceCode) + .submitVisibility(submitVisibility) + .build(); + + doNothing().when(baekjoonSubmitUsecase).judgement(any()); + + // when + final ResultActions perform = mockMvc.perform(post("/submit") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("submitVisibility: submitVisibility가 존재해야 합니다.")); + + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieControllerTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieControllerTest.java new file mode 100644 index 00000000..9c87eca1 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/controller/cookie/CookieControllerTest.java @@ -0,0 +1,58 @@ +package kr.co.morandi.backend.judgement.infrastructure.controller.cookie; + +import kr.co.morandi.backend.ControllerTestSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.test.web.servlet.ResultActions; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class CookieControllerTest extends ControllerTestSupport { + + @DisplayName("백준 쿠키 정보를 저장할 수 있다.") + @Test + void saveMemberBaekjoonCookie() throws Exception { + // given + String 쿠키 = "dummycookie"; + BaekjoonMemberCookieRequest request = new BaekjoonMemberCookieRequest(쿠키); + doNothing().when(baekjoonMemberCookieService).saveMemberBaekjoonCookie(any()); + + + // when + final ResultActions perform = mockMvc.perform(post("/cookie/baekjoon") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isOk()); + + } + + @DisplayName("cokkie 값이 빈 값일 경우 예외를 반환한다.") + @Test + void saveMemberBaekjoonCookieWithEmptyCookie() throws Exception { + // given + String 쿠키 = null; + BaekjoonMemberCookieRequest request = new BaekjoonMemberCookieRequest(쿠키); + doNothing().when(baekjoonMemberCookieService).saveMemberBaekjoonCookie(any()); + + + // when + final ResultActions perform = mockMvc.perform(post("/cookie/baekjoon") + .contentType("application/json") + .content(objectMapper.writeValueAsString(request))); + + // then + perform + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.success").value(false)) + .andExpect(jsonPath("$.response.message").value("cookie: Cookie 값은 비어 있을 수 없습니다.")); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepositoryTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepositoryTest.java new file mode 100644 index 00000000..d6d61f59 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonGlobalCookieRepositoryTest.java @@ -0,0 +1,52 @@ +package kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonCookie; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonGlobalCookie; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +@Transactional +class BaekjoonGlobalCookieRepositoryTest extends IntegrationTestSupport { + + @Autowired + private BaekjoonGlobalCookieRepository globalCookieRepository; + + @DisplayName("유효한 글로벌 쿠키를 찾을 수 있다.") + @Test + void findValidGlobalCookies() { + // given + LocalDateTime 현재_시간 = LocalDateTime.of(2021, 1, 1, 0, 0, 0); + + BaekjoonCookie 백준_쿠키 = BaekjoonCookie.builder() + .cookie("cookie") + .nowDateTime(현재_시간) + .build(); + + BaekjoonGlobalCookie 글로벌_관리_쿠키 = BaekjoonGlobalCookie.builder() + .baekjoonCookie(백준_쿠키) + .globalUserId("globalUserId") + .baekjoonRefreshToken("refreshToken") + .build(); + globalCookieRepository.save(글로벌_관리_쿠키); + + // when + final List validGlobalCookies = globalCookieRepository.findValidGlobalCookies(현재_시간); + + + // then + assertThat(validGlobalCookies).isNotEmpty() + .extracting("baekjoonCookie", "globalUserId", "baekjoonRefreshToken") + .containsExactly(tuple(백준_쿠키, "globalUserId", "refreshToken")); + + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepositoryTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepositoryTest.java new file mode 100644 index 00000000..6b35c4ef --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/baekjoon/BaekjoonMemberCookieRepositoryTest.java @@ -0,0 +1,55 @@ +package kr.co.morandi.backend.judgement.infrastructure.persistence.baekjoon; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonCookie; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.cookie.BaekjoonMemberCookie; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class BaekjoonMemberCookieRepositoryTest extends IntegrationTestSupport { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private BaekjoonMemberCookieRepository baekjoonMemberCookieRepository; + + @DisplayName("사용자의 Id로 백준 쿠키를 찾을 수 있다.") + @Test + void findBaekjoonMemberCookieByMemberId() { + // given + Member 사용자 = TestMemberFactory.createMember(); + String 쿠키 = "dummyCookie"; + + BaekjoonMemberCookie 백준_사용자_쿠키 = BaekjoonMemberCookie.builder() + .member(사용자) + .cookie(쿠키) + .nowDateTime(LocalDateTime.of(2021, 1, 1, 0, 0)) + .build(); + + memberRepository.save(사용자); + baekjoonMemberCookieRepository.save(백준_사용자_쿠키); + + // when + Optional maybeBaekjoonMemberCookie = baekjoonMemberCookieRepository.findBaekjoonMemberCookieByMember_MemberId(사용자.getMemberId()); + + // then + assertThat(maybeBaekjoonMemberCookie).isPresent() + .get() + .extracting("baekjoonCookie.value").isEqualTo("dummyCookie"); + + } + + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepositoryTest.java b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepositoryTest.java new file mode 100644 index 00000000..7f9b53c0 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/judgement/infrastructure/persistence/submit/BaekjoonSubmitRepositoryTest.java @@ -0,0 +1,149 @@ +package kr.co.morandi.backend.judgement.infrastructure.persistence.submit; + +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.infrastructure.persistence.dailydefense.DailyDefenseRepository; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.defense_record.infrastructure.persistence.dailydefense_record.DailyRecordRepository; +import kr.co.morandi.backend.factory.TestDefenseFactory; +import kr.co.morandi.backend.factory.TestMemberFactory; +import kr.co.morandi.backend.factory.TestProblemFactory; +import kr.co.morandi.backend.judgement.domain.model.baekjoon.submit.BaekjoonSubmit; +import kr.co.morandi.backend.judgement.domain.model.submit.SourceCode; +import kr.co.morandi.backend.judgement.domain.model.submit.SubmitVisibility; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.member_management.infrastructure.persistence.member.MemberRepository; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import kr.co.morandi.backend.problem_information.infrastructure.persistence.problem.ProblemRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; + +import static kr.co.morandi.backend.defense_management.domain.model.tempcode.model.Language.JAVA; +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +class BaekjoonSubmitRepositoryTest extends IntegrationTestSupport { + + @Autowired + private BaekjoonSubmitRepository baekjoonSubmitRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private ProblemRepository problemRepository; + + @Autowired + private DailyDefenseRepository dailyDefenseRepository; + + @Autowired + private DailyRecordRepository dailyRecordRepository; + + @DisplayName("Detail과 Record를 fetch 조인해서 제출을 찾아올 수 있다.") + @Test + void findSubmitJoinFetchDetailAndRecord() { + Member 사용자 = TestMemberFactory.createMember(); + memberRepository.save(사용자); + Map 문제 = TestProblemFactory.createProblems(5); + problemRepository.saveAll(문제.values()); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + dailyDefenseRepository.save(오늘의_문제); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + dailyRecordRepository.save(dailyRecord); + + LocalDateTime 제출시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + BaekjoonSubmit 백준_제출 = BaekjoonSubmit.builder() + .sourceCode(제출할_코드) + .member(사용자) + .submitDateTime(제출시간) + .detail(dailyRecord.getDetail(1L)) + .submitVisibility(SubmitVisibility.OPEN) + .build(); + + final BaekjoonSubmit 저장된_백준_제출 = baekjoonSubmitRepository.save(백준_제출); + + // when + final Optional 찾아온_백준_제출 = baekjoonSubmitRepository.findSubmitJoinFetchDetailAndRecord(저장된_백준_제출.getSubmitId()); + + + // then + assertThat(찾아온_백준_제출).isPresent() + .get() + .extracting("sourceCode.sourceCode", "sourceCode.language", + "member", "detail", "detail.submitCount", "detail.isSolved", + "detail.solvedTime", "submitVisibility", "detail.record.defense") + .containsExactly("code", JAVA, + 사용자, dailyRecord.getDetail(1L), 1L, false, + 0L, SubmitVisibility.OPEN, 오늘의_문제); + + + } + + @DisplayName("제출을 저장할 수 있다.") + @Test + void save() { + // given + Member 사용자 = TestMemberFactory.createMember(); + memberRepository.save(사용자); + Map 문제 = TestProblemFactory.createProblems(5); + problemRepository.saveAll(문제.values()); + DailyDefense 오늘의_문제 = TestDefenseFactory.createDailyDefense(문제); + dailyDefenseRepository.save(오늘의_문제); + + Map 시도할_문제 = Map.of(1L, 문제.get(1L)); + + DailyRecord dailyRecord = DailyRecord.builder() + .date(LocalDateTime.of(2021, 1, 1, 0, 0)) + .problems(시도할_문제) + .defense(오늘의_문제) + .member(사용자) + .build(); + dailyRecordRepository.save(dailyRecord); + + LocalDateTime 제출시간 = LocalDateTime.of(2021, 1, 1, 0, 0); + + SourceCode 제출할_코드 = SourceCode.builder() + .sourceCode("code") + .language(JAVA) + .build(); + + BaekjoonSubmit 백준_제출 = BaekjoonSubmit.builder() + .sourceCode(제출할_코드) + .member(사용자) + .submitDateTime(제출시간) + .detail(dailyRecord.getDetail(1L)) + .submitVisibility(SubmitVisibility.OPEN) + .build(); + + + // when + final BaekjoonSubmit 저장된_백준_제출 = baekjoonSubmitRepository.save(백준_제출); + + // then + assertThat(저장된_백준_제출) + .isNotNull() + .extracting("sourceCode.sourceCode", "sourceCode.language", "member", "detail", "submitVisibility", "detail.submitCount") + .containsExactly("code", JAVA, 사용자, dailyRecord.getDetail(1L), SubmitVisibility.OPEN, 1L); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java index 5f821f53..927bd9ed 100644 --- a/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/member_management/infrastructure/persistence/member/MemberRepositoryTest.java @@ -1,6 +1,7 @@ package kr.co.morandi.backend.member_management.infrastructure.persistence.member; import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.factory.TestMemberFactory; import kr.co.morandi.backend.member_management.domain.model.member.Member; import kr.co.morandi.backend.member_management.domain.model.member.SocialType; import org.junit.jupiter.api.AfterEach; @@ -8,20 +9,40 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@Transactional class MemberRepositoryTest extends IntegrationTestSupport { @Autowired private MemberRepository memberRepository; - @AfterEach - void tearDown() { - memberRepository.deleteAllInBatch(); + @DisplayName("사용자 ID로 사용자와 쿠키를 조인해서 조회할 수 있다.") + @Test + void findMemberJoinFetchCookie() { + // given + Member 사용자 = TestMemberFactory.createMember(); + 사용자.saveBaekjoonCookie("dummyCookie", LocalDateTime.of(2021, 1, 1, 0, 0, 0)); + final Member 저장된_사용자 = memberRepository.save(사용자); + + + // when + Optional 찾은_사용자 = memberRepository.findMemberJoinFetchCookie(저장된_사용자.getMemberId()); + + // then + assertThat(찾은_사용자).isPresent() + .get() + .extracting("baekjoonMemberCookie.baekjoonCookie.value", "baekjoonMemberCookie.baekjoonCookie.expiredAt") + .contains("dummyCookie", LocalDateTime.of(2021, 1, 1, 0, 0, 0).plusHours(6)); } + @DisplayName("닉네임이 이미 존재하면 true를 반환한다") @Test void existsByNickname() { diff --git a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java index 17d30b3e..45f3030d 100644 --- a/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java +++ b/src/test/java/kr/co/morandi/backend/problem_information/infrastructure/persistence/problem/ProblemRepositoryTest.java @@ -3,11 +3,11 @@ import kr.co.morandi.backend.IntegrationTestSupport; import kr.co.morandi.backend.defense_information.domain.model.defense.ProblemTier; import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -16,17 +16,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +@Transactional class ProblemRepositoryTest extends IntegrationTestSupport { @Autowired private ProblemRepository problemRepository; - @AfterEach - void tearDown() { - problemRepository.deleteAllInBatch(); - } - - @DisplayName("startTier와 endTier사이고, ACTIVE, dailyDefenseProblem에 속하지 않은 문제들을 가져올 수 있다.") @Test void findDailyDefenseProblems() { From 81a33d9fd5e31990b72f4b2ec6ebc0f59049e2ff Mon Sep 17 00:00:00 2001 From: Jeong Yong Choi <51875177+aj4941@users.noreply.github.com> Date: Sat, 27 Jul 2024 21:56:17 +0900 Subject: [PATCH 14/14] =?UTF-8?q?=EC=B1=84=EC=A0=90=20=ED=8C=8C=EC=9D=B4?= =?UTF-8?q?=ED=94=84=EB=9D=BC=EC=9D=B8=20=EA=B5=AC=EC=84=B1=20(AWS=20SQS,?= =?UTF-8?q?=20=08Redis=20Pub/Sub)=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init setting * :sparkles: AWS SQS와 Redis Pub/Sub을 통한 채점 파이프라인 코드 작성 * :pencil2: 코드 일부 수정 * :pencil2: Exception 관련 코드 일부 수정 * :sparkles: AWS SQS에 요청 정보를 넣는 테스트 코드 작 * :sparkles: Redis pub/sub 관련 테스트 코드 작성 * :sparkles: SQS 관련 테스트 코드 추가 * :pencil2: RedisListener 관련 테스트 수정 * :pencil2: Redis Pub/Sub과 관련된 코드 수정 * :pencil2: 테스트 코드에 ObjectMapper의 Mock을 제거하고 테스트 코드를 전반적으로 수정 * :pencil2: ObjectMapper와 관련한 테스트 코드 수정 및 SSE로 전송되는 메서드 매개변수 수정 * :pencil2: 예제 채점에 맞도록 URL을 example 내용이 들어가도록 수정 * :pencil2: MockBean을 이용하여 테스트 코드 수정 * :pencil2: CodeRequest, CodeResponse 구조 변경 * :sparkles: SQSService를 MessageQueueService의 구현체로 변경 * :pencil2: MessageResponse, CodeRequest 구조 변경 * :pencil2: Redis Pub/Sub 테스트 코드와 관련된 작업 수정 * :white_check_mark: SQSService와 관련된 재전송 로직 구현 및 테스트 코드 작성 * :pencil2: CodeRequest의 sseId를 defenseSessionId로 변경 * :white_check_mark: Redis Pub/Sub 메시지 재전송 로직 추가 및 테스트코드 작성 * :white_check_mark: CodeSubmitController 테스트 코드 수정 * :pencil2: CodeSubmitController.class를 ExampleCodeSubmitController.class로 변경 * :pencil2: AWS SQS 및 Redis Pub/Sub 관련 클래스명 수정 및 로직 일부 변경 * :pencil2: AWS SQS Region 명시 * :pencil2: AWS SQS Region 설정 변경 * :pencil2: AWS SQS Region 수정 * Update cicd-dev.yml * Update sonarcloud.yml * :pencil2: Elasticache 관련 테스트 Skip 하도록 설정 * :pencil2: Elasticache 관련 환경 변수 설정 테스트 * :white_check_mark: Elasticache 관련 테스트를 Skip 하도록 설정 * :pencil2: SecurityConfig 허용 URL 수정 --- .github/workflows/cicd-dev.yml | 7 +- .github/workflows/sonarcloud.yml | 2 + build.gradle | 8 + .../backend/common/config/RedisConfig.java | 49 ++++ .../defensemessaging/DefenseMessagePort.java | 1 + .../response/codesubmit/CodeResponse.java | 27 +++ .../response/codesubmit/MessageResponse.java | 28 +++ .../codesubmit/ExampleCodeSubmitService.java | 7 + .../codesubmit/ExampleCodeSubscriber.java | 54 +++++ .../codesubmit/SQSCodeSubmitService.java | 53 +++++ .../DailyDefenseManagementUsecase.java | 212 +++++++++--------- .../domain/model/session/DefenseSession.java | 1 - .../DefenseMessageSseAdapter.java | 6 +- .../config/codesubmit/AmazonSqsConfig.java | 54 +++++ .../ExampleCodeSubmitController.java | 26 +++ .../exception/RedisMessageErrorCode.java | 16 ++ .../exception/SQSMessageErrorCode.java | 16 ++ .../request/codesubmit/CodeRequest.java | 30 +++ .../request/codesubmit/CodeResponse.java | 12 + .../exception/OAuthErrorCode.java | 1 - .../backend/ControllerTestSupport.java | 18 +- .../backend/IntegrationTestSupport.java | 2 +- .../DailyDefenseControllerTest.java | 2 - .../codesubmit/ExampleCodeSubscriberTest.java | 117 ++++++++++ .../codesubmit/SQSCodeSubmitServiceTest.java | 77 +++++++ .../ExampleCodeSubmitControllerTest.java | 33 +++ .../morandi/backend/docs/RestDocsSupport.java | 54 ++--- 27 files changed, 761 insertions(+), 152 deletions(-) create mode 100644 src/main/java/kr/co/morandi/backend/common/config/RedisConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/CodeResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/MessageResponse.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubmitService.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriber.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitService.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/config/codesubmit/AmazonSqsConfig.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitController.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/RedisMessageErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/SQSMessageErrorCode.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeRequest.java create mode 100644 src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeResponse.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriberTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitServiceTest.java create mode 100644 src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitControllerTest.java diff --git a/.github/workflows/cicd-dev.yml b/.github/workflows/cicd-dev.yml index 285da1a7..171c50e8 100644 --- a/.github/workflows/cicd-dev.yml +++ b/.github/workflows/cicd-dev.yml @@ -18,7 +18,8 @@ permissions: jobs: build-and-push: runs-on: ubuntu-latest - + env: + redis.enabled: "false" steps: - name: Checkout uses: actions/checkout@v3 @@ -66,6 +67,8 @@ jobs: name: Deploy to EC2 needs: build-and-push runs-on: ubuntu-latest + env: + redis.enabled: "true" steps: - name: appleboy SSH and Deploy to EC2 uses: appleboy/ssh-action@master # ssh 접속하는 오픈소스 @@ -77,4 +80,4 @@ jobs: port: 22 envs: EC2_BACKEND_HOST,GITHUB_SHA,ECR_REGISTRY script: | - ssh -o StrictHostKeyChecking=no ubuntu@$EC2_BACKEND_HOST "export TAG=$GITHUB_SHA && bash /home/ubuntu/morandi-backend/deploy.sh" \ No newline at end of file + ssh -o StrictHostKeyChecking=no ubuntu@$EC2_BACKEND_HOST "export TAG=$GITHUB_SHA && bash /home/ubuntu/morandi-backend/deploy.sh" diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 8096f392..b4d3741b 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -10,6 +10,8 @@ jobs: build: name: Build and analyze runs-on: ubuntu-latest + env: + redis.enabled: "false" steps: - uses: actions/checkout@v3 with: diff --git a/build.gradle b/build.gradle index 59f169d8..2fb723fe 100644 --- a/build.gradle +++ b/build.gradle @@ -121,6 +121,14 @@ dependencies { // RestDocs asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + + // AWS SQS + implementation group: 'org.springframework.cloud', name: 'spring-cloud-aws-messaging', version: '2.2.1.RELEASE' + implementation group: 'org.springframework.cloud', name: 'spring-cloud-aws-autoconfigure', version: '2.2.1.RELEASE' + implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.1.RELEASE' + implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1") + implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs' + implementation 'org.springframework:spring-messaging:6.1.3' } tasks.named('sonarqube').configure { diff --git a/src/main/java/kr/co/morandi/backend/common/config/RedisConfig.java b/src/main/java/kr/co/morandi/backend/common/config/RedisConfig.java new file mode 100644 index 00000000..2768c0fe --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/common/config/RedisConfig.java @@ -0,0 +1,49 @@ +package kr.co.morandi.backend.common.config; + +import kr.co.morandi.backend.defense_management.application.service.codesubmit.ExampleCodeSubscriber; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Slf4j +@Configuration +@EnableRedisRepositories +@ConditionalOnProperty(name = "redis.enabled", havingValue = "true", matchIfMissing = false) +public class RedisConfig { + + @Value("${redis.host}") + private String redisHost; + + @Value("${redis.port}") + private int redisPort; + @Bean + public RedisConnectionFactory redisConnectionFactory() { + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisHost, redisPort); + return lettuceConnectionFactory; + } + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } + @Bean + RedisMessageListenerContainer redisContainer(RedisConnectionFactory connectionFactory, + ExampleCodeSubscriber subscriber) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.addMessageListener(subscriber, new ChannelTopic("channel")); + return container; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java index 4f01f835..fff02006 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/port/out/defensemessaging/DefenseMessagePort.java @@ -1,5 +1,6 @@ package kr.co.morandi.backend.defense_management.application.port.out.defensemessaging; +import kr.co.morandi.backend.defense_management.application.response.codesubmit.CodeResponse; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; public interface DefenseMessagePort { diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/CodeResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/CodeResponse.java new file mode 100644 index 00000000..e4262882 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/CodeResponse.java @@ -0,0 +1,27 @@ +package kr.co.morandi.backend.defense_management.application.response.codesubmit; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +public class CodeResponse { + + private String result; + private double executeTime; + private String output; + + public static CodeResponse create(MessageResponse messageResponse) { + return CodeResponse.builder() + .result(messageResponse.getResult()) + .executeTime(messageResponse.getExecute_time()) + .output(messageResponse.getOutput()) + .build(); + } + @Builder + private CodeResponse(String result, double executeTime, String output) { + this.result = result; + this.executeTime = executeTime; + this.output = output; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/MessageResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/MessageResponse.java new file mode 100644 index 00000000..d1152cd1 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/response/codesubmit/MessageResponse.java @@ -0,0 +1,28 @@ +package kr.co.morandi.backend.defense_management.application.response.codesubmit; + +import lombok.*; + +@Getter +public class MessageResponse { + + private String result; + private double execute_time; + private String output; + private String sseId; + + public static MessageResponse create(String result, double execute_time, String output, String sseId) { + return MessageResponse.builder() + .result(result) + .execute_time(execute_time) + .output(output) + .sseId(sseId) + .build(); + } + @Builder + private MessageResponse(String result, double execute_time, String output, String sseId) { + this.result = result; + this.execute_time = execute_time; + this.output = output; + this.sseId = sseId; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubmitService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubmitService.java new file mode 100644 index 00000000..1681a1da --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubmitService.java @@ -0,0 +1,7 @@ +package kr.co.morandi.backend.defense_management.application.service.codesubmit; + +import kr.co.morandi.backend.defense_management.infrastructure.request.codesubmit.CodeRequest; + +public interface ExampleCodeSubmitService { + void submitCodeToQueue(CodeRequest codeRequest); +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriber.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriber.java new file mode 100644 index 00000000..33c87c9a --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriber.java @@ -0,0 +1,54 @@ +package kr.co.morandi.backend.defense_management.application.service.codesubmit; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.application.port.out.defensemessaging.DefenseMessagePort; +import kr.co.morandi.backend.defense_management.application.response.codesubmit.CodeResponse; +import kr.co.morandi.backend.defense_management.application.response.codesubmit.MessageResponse; +import kr.co.morandi.backend.defense_management.infrastructure.exception.RedisMessageErrorCode; +import kr.co.morandi.backend.defense_management.infrastructure.exception.SQSMessageErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +@Slf4j +public class ExampleCodeSubscriber implements MessageListener { + + private final ObjectMapper objectMapper; + + private final DefenseMessagePort defenseMessagePort; + private static final int MAX_RETRIES = 3; + + @Override + public void onMessage(Message message, byte[] pattern) { + try { + String resultString = new String(message.getBody()); + MessageResponse messageResponse = objectMapper.readValue(resultString, MessageResponse.class); + String jsonMessage = objectMapper.writeValueAsString(CodeResponse.create(messageResponse)); + defenseMessagePort.sendMessage(Long.valueOf(messageResponse.getSseId()), jsonMessage); + } catch (JsonProcessingException | NullPointerException e) { + throw new MorandiException(RedisMessageErrorCode.MESSAGE_PARSE_ERROR); + } catch (Exception e) { + retrySendMessage(message, MAX_RETRIES); + } + } + public void retrySendMessage(Message message, int count) { + if (count == 0) + throw new MorandiException(RedisMessageErrorCode.MESSAGE_SEND_ERROR); + try { + String resultString = new String(message.getBody()); + MessageResponse messageResponse = objectMapper.readValue(resultString, MessageResponse.class); + String jsonMessage = objectMapper.writeValueAsString(CodeResponse.create(messageResponse)); + defenseMessagePort.sendMessage(Long.valueOf(messageResponse.getSseId()), jsonMessage); + } catch (JsonProcessingException e) { + throw new MorandiException(SQSMessageErrorCode.MESSAGE_PARSE_ERROR); + } catch (Exception e) { + retrySendMessage(message, count - 1); + } + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitService.java b/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitService.java new file mode 100644 index 00000000..f6e7bc9f --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitService.java @@ -0,0 +1,53 @@ +package kr.co.morandi.backend.defense_management.application.service.codesubmit; + +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import com.amazonaws.services.sqs.model.SendMessageResult; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.infrastructure.exception.SQSMessageErrorCode; +import kr.co.morandi.backend.defense_management.infrastructure.request.codesubmit.CodeRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class SQSCodeSubmitService implements ExampleCodeSubmitService { + + @Value("${cloud.aws.sqs.queue.example-compile-url}") + private String url; + + private final AmazonSQS amazonSQS; + + private final ObjectMapper objectMapper; + private static final int MAX_RETRIES = 3; + @Override + public void submitCodeToQueue(CodeRequest codeRequest) { + try { + String requestString = objectMapper.writeValueAsString(codeRequest); + SendMessageRequest sendMessageRequest = new SendMessageRequest(url, requestString); + amazonSQS.sendMessage(sendMessageRequest); + } catch (JsonProcessingException e) { + throw new MorandiException(SQSMessageErrorCode.MESSAGE_PARSE_ERROR); + } catch (Exception e) { + // 재전송 로직 추가 + retrySendMessage(codeRequest, MAX_RETRIES); + } + } + public void retrySendMessage(CodeRequest codeRequest, int count) { + if (count == 0) + throw new MorandiException(SQSMessageErrorCode.MESSAGE_SEND_FAILED); + + try { + String requestString = objectMapper.writeValueAsString(codeRequest); + SendMessageRequest sendMessageRequest = new SendMessageRequest(url, requestString); + amazonSQS.sendMessage(sendMessageRequest); + } catch (JsonProcessingException e) { + throw new MorandiException(SQSMessageErrorCode.MESSAGE_PARSE_ERROR); + } catch (Exception e) { + retrySendMessage(codeRequest, count - 1); + } + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/application/usecase/session/DailyDefenseManagementUsecase.java b/src/main/java/kr/co/morandi/backend/defense_management/application/usecase/session/DailyDefenseManagementUsecase.java index 6f31cbf4..26cef926 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/application/usecase/session/DailyDefenseManagementUsecase.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/application/usecase/session/DailyDefenseManagementUsecase.java @@ -1,116 +1,116 @@ - package kr.co.morandi.backend.defense_management.application.usecase.session; - - import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; - import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; - import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; - import kr.co.morandi.backend.defense_management.application.mapper.session.StartDailyDefenseMapper; - import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; - import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; - import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; - import kr.co.morandi.backend.defense_management.domain.event.CreateDefenseMessageEvent; - import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; - import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; - import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; - import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; - import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; - import kr.co.morandi.backend.member_management.domain.model.member.Member; - import kr.co.morandi.backend.problem_information.application.port.out.problemcontent.ProblemContentPort; - import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; - import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; - import lombok.RequiredArgsConstructor; - import org.springframework.context.ApplicationEventPublisher; - import org.springframework.stereotype.Service; - import org.springframework.transaction.annotation.Transactional; - - import java.time.LocalDateTime; - import java.util.Map; - import java.util.Optional; - - import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; - - @Service - @Transactional(readOnly = true) - @RequiredArgsConstructor - public class DailyDefenseManagementUsecase { - - private final DailyDefensePort dailyDefensePort; - private final DailyRecordPort dailyRecordPort; - private final ProblemGenerationService problemGenerationService; - private final DefenseSessionPort defenseSessionPort; - private final MemberPort memberPort; - private final ProblemContentPort problemContentPort; - private final ApplicationEventPublisher publisher; - - @Transactional - public StartDailyDefenseResponse startDailyDefense(StartDailyDefenseServiceRequest request, Long memberId, LocalDateTime requestTime) { - Long problemNumber = request.getProblemNumber(); - Member member = memberPort.findMemberById(memberId); - - // 세션이랑 세션 Detail을 찾아서 응시 기록이 있는지 살펴보기 - final Optional maybeDefenseSession = defenseSessionPort.findTodaysDailyDefenseSession(member, requestTime); - - // 오늘의 Defense를 찾아오기 - final DailyDefense dailyDefense = dailyDefensePort.findDailyDefense(DAILY, requestTime.toLocalDate()); - - // 오늘의 문제 목록 중에서 원하는 문제를 찾아서 시도하려는 문제 목록에 추가 (오늘의 문제 목록에 해당 문제가 없으면 예외 발생) - final Map tryProblem = dailyDefense.getTryingProblem(problemNumber, problemGenerationService); - - // DefenseSession이 있으면 get, 없으면 새로운 DefenseSession을 생성 - final DefenseSession defenseSession = maybeDefenseSession.orElseGet(() -> createNewSession(member, requestTime, dailyDefense, tryProblem)); - - // DefenseSession의 recordId로 DailyRecord를 찾고 문제를 시도했는지 확인하고 시도하지 않았으면 시도하도록 함 - Long recordId = defenseSession.getRecordId(); - DailyRecord dailyRecord = dailyRecordPort.findDailyRecord(member, recordId, requestTime.toLocalDate()) - .orElseThrow(() -> new IllegalArgumentException("세션에 해당하는 응시 기록이 없습니다.")); - - if (!defenseSession.hasTriedProblem(problemNumber)) { - dailyRecord.tryMoreProblem(tryProblem); - defenseSession.tryMoreProblem(problemNumber, requestTime); - } - - final DefenseSession savedDefenseSession = defenseSessionPort.saveDefenseSession(defenseSession); - - - // 문제 내용 가져오기 - final Map problemContent = getProblemContents(tryProblem); - - // 문제 목록을 DefenseProblemResponse DTO로 변환 - return StartDailyDefenseMapper.of(tryProblem, dailyDefense, savedDefenseSession, dailyRecord, problemContent); +package kr.co.morandi.backend.defense_management.application.usecase.session; + +import kr.co.morandi.backend.defense_information.application.port.out.dailydefense.DailyDefensePort; +import kr.co.morandi.backend.defense_information.domain.model.dailydefense.DailyDefense; +import kr.co.morandi.backend.defense_information.domain.service.defense.ProblemGenerationService; +import kr.co.morandi.backend.defense_management.application.mapper.session.StartDailyDefenseMapper; +import kr.co.morandi.backend.defense_management.application.port.out.session.DefenseSessionPort; +import kr.co.morandi.backend.defense_management.application.request.session.StartDailyDefenseServiceRequest; +import kr.co.morandi.backend.defense_management.application.response.session.StartDailyDefenseResponse; +import kr.co.morandi.backend.defense_management.domain.event.CreateDefenseMessageEvent; +import kr.co.morandi.backend.defense_management.domain.event.DefenseStartTimerEvent; +import kr.co.morandi.backend.defense_management.domain.model.session.DefenseSession; +import kr.co.morandi.backend.defense_record.application.port.out.dailyrecord.DailyRecordPort; +import kr.co.morandi.backend.defense_record.domain.model.dailydefense_record.DailyRecord; +import kr.co.morandi.backend.member_management.application.port.out.member.MemberPort; +import kr.co.morandi.backend.member_management.domain.model.member.Member; +import kr.co.morandi.backend.problem_information.application.port.out.problemcontent.ProblemContentPort; +import kr.co.morandi.backend.problem_information.application.response.problemcontent.ProblemContent; +import kr.co.morandi.backend.problem_information.domain.model.problem.Problem; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; + +import static kr.co.morandi.backend.defense_information.domain.model.defense.DefenseType.DAILY; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class DailyDefenseManagementUsecase { + + private final DailyDefensePort dailyDefensePort; + private final DailyRecordPort dailyRecordPort; + private final ProblemGenerationService problemGenerationService; + private final DefenseSessionPort defenseSessionPort; + private final MemberPort memberPort; + private final ProblemContentPort problemContentPort; + private final ApplicationEventPublisher publisher; + + @Transactional + public StartDailyDefenseResponse startDailyDefense(StartDailyDefenseServiceRequest request, Long memberId, LocalDateTime requestTime) { + Long problemNumber = request.getProblemNumber(); + Member member = memberPort.findMemberById(memberId); + + // 세션이랑 세션 Detail을 찾아서 응시 기록이 있는지 살펴보기 + final Optional maybeDefenseSession = defenseSessionPort.findTodaysDailyDefenseSession(member, requestTime); + + // 오늘의 Defense를 찾아오기 + final DailyDefense dailyDefense = dailyDefensePort.findDailyDefense(DAILY, requestTime.toLocalDate()); + + // 오늘의 문제 목록 중에서 원하는 문제를 찾아서 시도하려는 문제 목록에 추가 (오늘의 문제 목록에 해당 문제가 없으면 예외 발생) + final Map tryProblem = dailyDefense.getTryingProblem(problemNumber, problemGenerationService); + + // DefenseSession이 있으면 get, 없으면 새로운 DefenseSession을 생성 + final DefenseSession defenseSession = maybeDefenseSession.orElseGet(() -> createNewSession(member, requestTime, dailyDefense, tryProblem)); + + // DefenseSession의 recordId로 DailyRecord를 찾고 문제를 시도했는지 확인하고 시도하지 않았으면 시도하도록 함 + Long recordId = defenseSession.getRecordId(); + DailyRecord dailyRecord = dailyRecordPort.findDailyRecord(member, recordId, requestTime.toLocalDate()) + .orElseThrow(() -> new IllegalArgumentException("세션에 해당하는 응시 기록이 없습니다.")); + + if (!defenseSession.hasTriedProblem(problemNumber)) { + dailyRecord.tryMoreProblem(tryProblem); + defenseSession.tryMoreProblem(problemNumber, requestTime); } - /* - * 백준 문제 ID 목록을 받아서 문제 내용을 가져오는 메소드 - * */ - private Map getProblemContents(Map tryProblem) { - return problemContentPort.getProblemContents(tryProblem.values() - .stream() - .map(Problem::getBaekjoonProblemId) - .toList()); - } + final DefenseSession savedDefenseSession = defenseSessionPort.saveDefenseSession(defenseSession); - /* - * 세션이 존재하지 않을 경우 새롭게 시험을 시작하는 메소드 - * */ - private DefenseSession createNewSession(Member member, LocalDateTime now, DailyDefense dailyDefense, Map tryProblem) { - DailyRecord dailyRecord = DailyRecord.tryDefense(now, dailyDefense, member, tryProblem); - DailyRecord savedDailyRecord = dailyRecordPort.saveDailyRecord(dailyRecord); - Long recordId = savedDailyRecord.getRecordId(); - final DefenseSession defenseSession = defenseSessionPort.saveDefenseSession( - DefenseSession.startSession(member, recordId, dailyDefense.getDefenseType(), tryProblem.keySet(), now, dailyDefense.getEndTime(now))); + // 문제 내용 가져오기 + final Map problemContent = getProblemContents(tryProblem); - /* - * DefenseSession에 관련된 타이머 시작 이벤트 발행 - * */ - publisher.publishEvent(new DefenseStartTimerEvent(defenseSession.getDefenseSessionId(), defenseSession.getStartDateTime(), defenseSession.getEndDateTime())); + // 문제 목록을 DefenseProblemResponse DTO로 변환 + return StartDailyDefenseMapper.of(tryProblem, dailyDefense, savedDefenseSession, dailyRecord, problemContent); + } - /* - * DefenseMessage 연결 이벤트 발행 (현재는 SSE로 구현되어 있습니다.) - * */ - publisher.publishEvent(new CreateDefenseMessageEvent(defenseSession.getDefenseSessionId())); + /* + * 백준 문제 ID 목록을 받아서 문제 내용을 가져오는 메소드 + * */ + private Map getProblemContents(Map tryProblem) { + return problemContentPort.getProblemContents(tryProblem.values() + .stream() + .map(Problem::getBaekjoonProblemId) + .toList()); + } - return defenseSession; - } + /* + * 세션이 존재하지 않을 경우 새롭게 시험을 시작하는 메소드 + * */ + private DefenseSession createNewSession(Member member, LocalDateTime now, DailyDefense dailyDefense, Map tryProblem) { + DailyRecord dailyRecord = DailyRecord.tryDefense(now, dailyDefense, member, tryProblem); + DailyRecord savedDailyRecord = dailyRecordPort.saveDailyRecord(dailyRecord); + Long recordId = savedDailyRecord.getRecordId(); + final DefenseSession defenseSession = defenseSessionPort.saveDefenseSession( + DefenseSession.startSession(member, recordId, dailyDefense.getDefenseType(), tryProblem.keySet(), now, dailyDefense.getEndTime(now))); + /* + * DefenseSession에 관련된 타이머 시작 이벤트 발행 + * */ + publisher.publishEvent(new DefenseStartTimerEvent(defenseSession.getDefenseSessionId(), defenseSession.getStartDateTime(), defenseSession.getEndDateTime())); + + /* + * DefenseMessage 연결 이벤트 발행 (현재는 SSE로 구현되어 있습니다.) + * */ + publisher.publishEvent(new CreateDefenseMessageEvent(defenseSession.getDefenseSessionId())); + + return defenseSession; } + + +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java index 93836589..f08e8b12 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/domain/model/session/DefenseSession.java @@ -126,5 +126,4 @@ private DefenseSession(Member member, Long recordId, DefenseType defenseType, Se .findFirst() .orElse(INITIAL_ACCESS_PROBLEM_NUMBER); } - } diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java index beeeaa1c..d20d901f 100644 --- a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/adapter/defensemessaging/DefenseMessageSseAdapter.java @@ -2,6 +2,7 @@ import kr.co.morandi.backend.common.exception.MorandiException; import kr.co.morandi.backend.defense_management.application.port.out.defensemessaging.DefenseMessagePort; +import kr.co.morandi.backend.defense_management.application.response.codesubmit.CodeResponse; import kr.co.morandi.backend.defense_management.domain.error.SessionErrorCode; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -37,7 +38,6 @@ public void createConnection(Long defenseSessionId) { public SseEmitter getConnection(Long defenseSessionId) { emitters.putIfAbsent(defenseSessionId, createSseEmitter(defenseSessionId)); - final SseEmitter sseEmitter = emitters.get(defenseSessionId); // init 메세지 전송 @@ -55,8 +55,6 @@ public SseEmitter getConnection(Long defenseSessionId) { return emitters.get(defenseSessionId); } - - /* * 메세지 보낼 떄 사용하면 됩니다. defenseSessionId를 기준으로 SseEmitter를 찾아서 해당 SseEmitter에 message를 전송합니다. * (message에 직렬화하여 전송) @@ -77,7 +75,6 @@ public boolean sendMessage(Long defenseSessionId, String message) { emitters.remove(defenseSessionId); } } - return false; } @@ -117,7 +114,6 @@ private SseEmitter createSseEmitter(Long defenseSessionId) { return emitter; } - /** * 재시도 로직을 구현했습니다. * 재귀 호출로 최대 3번까지 재시도 diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/config/codesubmit/AmazonSqsConfig.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/config/codesubmit/AmazonSqsConfig.java new file mode 100644 index 00000000..abf1ea18 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/config/codesubmit/AmazonSqsConfig.java @@ -0,0 +1,54 @@ +package kr.co.morandi.backend.defense_management.infrastructure.config.codesubmit; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.AmazonSQSClientBuilder; +import io.awspring.cloud.sqs.operations.SqsTemplate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sqs.SqsAsyncClient; + +@Slf4j +@Configuration +public class AmazonSqsConfig { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public SqsAsyncClient sqsAsyncClient(){ + return SqsAsyncClient + .builder() + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider + .create(AwsBasicCredentials.create(accessKey, secretKey))) + .build(); + } + + @Bean + public SqsTemplate sqsTemplate(SqsAsyncClient sqsAsyncClient){ + return SqsTemplate.builder().sqsAsyncClient(sqsAsyncClient).build(); + } + + @Bean + public AmazonSQS amazonSQS() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return AmazonSQSClientBuilder.standard() + .withRegion(Regions.AP_NORTHEAST_2) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitController.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitController.java new file mode 100644 index 00000000..0ece9544 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitController.java @@ -0,0 +1,26 @@ +package kr.co.morandi.backend.defense_management.infrastructure.controller; + +import kr.co.morandi.backend.defense_management.application.service.codesubmit.ExampleCodeSubmitService; +import kr.co.morandi.backend.defense_management.infrastructure.request.codesubmit.CodeRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +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; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/submit") +public class ExampleCodeSubmitController { + + private final ExampleCodeSubmitService exampleCodeQueueService; + + @PostMapping("/example") + public ResponseEntity submit(@RequestBody CodeRequest codeRequest) { + exampleCodeQueueService.submitCodeToQueue(codeRequest); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/RedisMessageErrorCode.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/RedisMessageErrorCode.java new file mode 100644 index 00000000..9343e5e2 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/RedisMessageErrorCode.java @@ -0,0 +1,16 @@ +package kr.co.morandi.backend.defense_management.infrastructure.exception; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +@Getter +@RequiredArgsConstructor +public enum RedisMessageErrorCode implements ErrorCode { + + MESSAGE_PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Redis의 메시지를 파싱하지 못했습니다."), + MESSAGE_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Redis 메시지를 전송하지 못했습니다."); + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/SQSMessageErrorCode.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/SQSMessageErrorCode.java new file mode 100644 index 00000000..55cf624c --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/exception/SQSMessageErrorCode.java @@ -0,0 +1,16 @@ +package kr.co.morandi.backend.defense_management.infrastructure.exception; + +import kr.co.morandi.backend.common.exception.errorcode.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum SQSMessageErrorCode implements ErrorCode { + MESSAGE_PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "AWS SQS에 보낼 메시지를 파싱하지 못했습니다."), + MESSAGE_SEND_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AWS SQS에 메시지를 전송하지 못했습니다."); + + private final HttpStatus httpStatus; + private final String message; +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeRequest.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeRequest.java new file mode 100644 index 00000000..77adbd45 --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeRequest.java @@ -0,0 +1,30 @@ +package kr.co.morandi.backend.defense_management.infrastructure.request.codesubmit; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +public class CodeRequest { + + private String code; + private String language; + private String input; + private String defenseSessionId; + + public static CodeRequest create(String code, String language, String input, String defenseSessionId) { + return CodeRequest.builder() + .code(code) + .language(language) + .input(input) + .defenseSessionId(defenseSessionId) + .build(); + } + @Builder + private CodeRequest(String code, String language, String input, String defenseSessionId) { + this.code = code; + this.language = language; + this.input = input; + this.defenseSessionId = defenseSessionId; + } +} diff --git a/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeResponse.java b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeResponse.java new file mode 100644 index 00000000..9299dd5b --- /dev/null +++ b/src/main/java/kr/co/morandi/backend/defense_management/infrastructure/request/codesubmit/CodeResponse.java @@ -0,0 +1,12 @@ +package kr.co.morandi.backend.defense_management.infrastructure.request.codesubmit; + +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class CodeResponse { + + private String result; + private String executeTime; + private String output; +} diff --git a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java index 5e09e8a5..79311c88 100644 --- a/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java +++ b/src/main/java/kr/co/morandi/backend/member_management/infrastructure/exception/OAuthErrorCode.java @@ -19,7 +19,6 @@ public enum OAuthErrorCode implements ErrorCode { UNKNOWN_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,"알 수 없는 오류"), GOOGLE_OAUTH_ERROR(HttpStatus.BAD_REQUEST,"구글 OAuth 인증을 실패했습니다."); - private final HttpStatus httpStatus; private final String message; } \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java index d8570158..f42bc799 100644 --- a/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java +++ b/src/test/java/kr/co/morandi/backend/ControllerTestSupport.java @@ -3,7 +3,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import kr.co.morandi.backend.defense_information.application.port.in.DailyDefenseUseCase; import kr.co.morandi.backend.defense_information.infrastructure.controller.DailyDefenseController; +import kr.co.morandi.backend.defense_management.application.service.codesubmit.ExampleCodeSubmitService; import kr.co.morandi.backend.defense_management.application.service.message.DefenseMessageService; +import kr.co.morandi.backend.defense_management.infrastructure.controller.ExampleCodeSubmitController; import kr.co.morandi.backend.defense_management.infrastructure.controller.SessionConnectionController; import kr.co.morandi.backend.defense_record.application.port.in.DailyRecordRankUseCase; import kr.co.morandi.backend.defense_record.infrastructure.controller.DailyRecordController; @@ -27,14 +29,14 @@ DailyRecordController.class, SessionConnectionController.class, CookieController.class, - BaekjoonSubmitController.class -}, + BaekjoonSubmitController.class, + ExampleCodeSubmitController.class }, excludeAutoConfiguration = SecurityAutoConfiguration.class, excludeFilters = { - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { - OncePerRequestFilter.class - }) -} + @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { + OncePerRequestFilter.class + }) + } ) @ActiveProfiles("test") public abstract class ControllerTestSupport { @@ -68,5 +70,7 @@ public abstract class ControllerTestSupport { @MockBean protected BaekjoonSubmitUsecase baekjoonSubmitUsecase; - + // ExampleCodeSubmitController + @MockBean + protected ExampleCodeSubmitService messagingQueueService; } diff --git a/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java b/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java index 99ec4e93..b316c65f 100644 --- a/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java +++ b/src/test/java/kr/co/morandi/backend/IntegrationTestSupport.java @@ -3,6 +3,7 @@ import kr.co.morandi.backend.config.WebClientTestConfig; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; +import com.amazonaws.services.sqs.AmazonSQS; import org.springframework.test.context.ActiveProfiles; @ActiveProfiles("test") @@ -11,5 +12,4 @@ WebClientTestConfig.class }) public abstract class IntegrationTestSupport { - } diff --git a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java index ff01117a..1a2d851f 100644 --- a/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java +++ b/src/test/java/kr/co/morandi/backend/defense_information/infrastructure/controller/DailyDefenseControllerTest.java @@ -15,8 +15,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class DailyDefenseControllerTest extends ControllerTestSupport { - - @DisplayName("DailyDefense 정보를 로그인하지 않은 상태에서 가져올 수 있다.") @Test void getDailyDefenseInfo() throws Exception { diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriberTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriberTest.java new file mode 100644 index 00000000..21ba1632 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/ExampleCodeSubscriberTest.java @@ -0,0 +1,117 @@ +package kr.co.morandi.backend.defense_management.application.service.codesubmit; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.application.port.out.defensemessaging.DefenseMessagePort; +import kr.co.morandi.backend.defense_management.application.response.codesubmit.MessageResponse; +import kr.co.morandi.backend.defense_management.infrastructure.exception.RedisMessageErrorCode; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.redis.connection.DefaultMessage; +import org.springframework.data.redis.connection.Message; +import org.springframework.test.context.ActiveProfiles; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +@ExtendWith(MockitoExtension.class) +@ActiveProfiles("test") +@DisabledIfEnvironmentVariable(named = "redis.enabled", matches = "false") +class ExampleCodeSubscriberTest extends IntegrationTestSupport { + + @MockBean + private DefenseMessagePort defenseMessagePort; + + @Autowired + private ExampleCodeSubscriber redisMessageSubscriber; + + @Autowired + private ObjectMapper objectMapper; + + @DisplayName("Redis pub/sub에 메시지가 도착하면 메시지가 정상적으로 SSE에 전송되어야 한다.") + @Test + void correctOnMessage() throws JsonProcessingException { + // given + MessageResponse messageResponse + = MessageResponse.create("성공", 0.2, "Hello world", "123"); + + String json = objectMapper.writeValueAsString(messageResponse); + String channel = "channel"; + Message message = new DefaultMessage(channel.getBytes(), json.getBytes()); + + when(defenseMessagePort.sendMessage(anyLong(), anyString())) + .thenReturn(true); + + // when + redisMessageSubscriber.onMessage(message, null); + + // then + verify(defenseMessagePort).sendMessage(eq(123L), any(String.class)); + } + + @DisplayName("Redis 메시지 전송 시에 예외가 발생하더라도 3번 이내에 재전송에 성공하면 정상적으로 전송된다.") + @Test + void retrySendMessageTest() throws JsonProcessingException { + // given + MessageResponse messageResponse + = MessageResponse.create("성공", 0.2, "Hello world", "123"); + + String json = objectMapper.writeValueAsString(messageResponse); + String channel = "channel"; + Message message = new DefaultMessage(channel.getBytes(), json.getBytes()); + + when(defenseMessagePort.sendMessage(anyLong(), anyString())) + .thenThrow(new RuntimeException("Error")) + .thenThrow(new RuntimeException("Error")) + .thenReturn(true); + + // when + redisMessageSubscriber.onMessage(message, null); + + // then + verify(defenseMessagePort, times(1 + 2)).sendMessage(eq(123L), any(String.class)); + } + + @DisplayName("Redis 메시지 재전송 로직이 3번 실패하면 예외가 발생한다.") + @Test + void retrySendMessageFailTest() throws JsonProcessingException { + // given + MessageResponse messageResponse + = MessageResponse.create("성공", 0.2, "Hello world", "123"); + + String json = objectMapper.writeValueAsString(messageResponse); + String channel = "channel"; + Message message = new DefaultMessage(channel.getBytes(), json.getBytes()); + + when(defenseMessagePort.sendMessage(anyLong(), anyString())) + .thenThrow(new RuntimeException("Error")); + + // when + MorandiException exception = assertThrows(MorandiException.class, + () -> redisMessageSubscriber.onMessage(message, null)); + + // then + assertEquals(RedisMessageErrorCode.MESSAGE_SEND_ERROR, exception.getErrorCode()); + verify(defenseMessagePort, times(1 + 3)).sendMessage(eq(123L), any(String.class)); + } + + @DisplayName("Redis pub/sub에 형식에 맞지 않는 메시지가 오면 예외를 발생시켜야 한다.") + @Test + void incorrectOnMessage() { + // when & then + MorandiException morandiException = assertThrows(MorandiException.class, + () -> redisMessageSubscriber.onMessage(null, null)); + assertEquals(RedisMessageErrorCode.MESSAGE_PARSE_ERROR, morandiException.getErrorCode()); + assertEquals("Redis의 메시지를 파싱하지 못했습니다.", morandiException.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitServiceTest.java b/src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitServiceTest.java new file mode 100644 index 00000000..0417e173 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/application/service/codesubmit/SQSCodeSubmitServiceTest.java @@ -0,0 +1,77 @@ +package kr.co.morandi.backend.defense_management.application.service.codesubmit; + +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import com.amazonaws.services.sqs.model.SendMessageResult; +import kr.co.morandi.backend.IntegrationTestSupport; +import kr.co.morandi.backend.common.exception.MorandiException; +import kr.co.morandi.backend.defense_management.infrastructure.exception.SQSMessageErrorCode; +import kr.co.morandi.backend.defense_management.infrastructure.request.codesubmit.CodeRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class SQSCodeSubmitServiceTest extends IntegrationTestSupport { + + @Autowired + private SQSCodeSubmitService sqsService; + + @MockBean + private AmazonSQS amazonSQS; + + @DisplayName("사용자가 소스코드를 제출하면 AWS SQS에 JSON 메시지가 올바른 주소에 전송된다.") + @Test + void correctSendMessage() { + // given + CodeRequest 코드_요청_정보 = CodeRequest.create("Hello world", "Python", "", "123"); + + SendMessageResult 전송_결과물 = new SendMessageResult().withMessageId("12345"); + when(amazonSQS.sendMessage(any(SendMessageRequest.class))).thenReturn(전송_결과물); + + // when & then + assertDoesNotThrow(() -> sqsService.submitCodeToQueue(코드_요청_정보)); + } + + @DisplayName("AWS SQS 메시지 전송 시에 예외가 발생하더라도 3번 이내에 재전송에 성공하면 정상적으로 전송된다.") + @Test + void retrySendMessageTest() { + // given + CodeRequest 코드_요청_정보 = CodeRequest.create("Hello world", "Python", "", "123"); + SendMessageResult 전송_결과물 = new SendMessageResult().withMessageId("12345"); + + when(amazonSQS.sendMessage(any(SendMessageRequest.class))) + .thenThrow(new RuntimeException("SQS Exception")) + .thenThrow(new RuntimeException("SQS Exception")) + .thenReturn(전송_결과물); + + // when & then + assertDoesNotThrow(() -> sqsService.submitCodeToQueue(코드_요청_정보)); + verify(amazonSQS, times(1 + 2)).sendMessage(any(SendMessageRequest.class)); + } + + @DisplayName("AWS SQS 메시지 재전송 로직이 3번 실패하면 예외가 발생한다.") + @Test + void retrySendMessageFailTest() { + // given + CodeRequest 코드_요청_정보 = CodeRequest.create("Hello world", "Python", "", "123"); + + when(amazonSQS.sendMessage(any(SendMessageRequest.class))) + .thenThrow(new RuntimeException("SQS Exception")); + + // when & then + MorandiException exception = assertThrows(MorandiException.class, () -> { + sqsService.submitCodeToQueue(코드_요청_정보); + }); + + assertEquals(SQSMessageErrorCode.MESSAGE_SEND_FAILED, exception.getErrorCode()); + verify(amazonSQS, times(1 + 3)).sendMessage(any(SendMessageRequest.class)); + } +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitControllerTest.java b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitControllerTest.java new file mode 100644 index 00000000..6261a290 --- /dev/null +++ b/src/test/java/kr/co/morandi/backend/defense_management/infrastructure/controller/ExampleCodeSubmitControllerTest.java @@ -0,0 +1,33 @@ +package kr.co.morandi.backend.defense_management.infrastructure.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import kr.co.morandi.backend.ControllerTestSupport; +import kr.co.morandi.backend.defense_management.infrastructure.request.codesubmit.CodeRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.ResultActions; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class ExampleCodeSubmitControllerTest extends ControllerTestSupport { + + @Autowired + private ObjectMapper objectMapper; + + @DisplayName("소스코드를 제출했을 때 정상적인 요청이라면 200 OK 실행이 된다.") + @Test + public void testSubmitCodeRequest() throws Exception { + // given + CodeRequest codeRequest = CodeRequest.create("Hello world", "Python", "", "123"); + + // when & then + ResultActions perform = mockMvc.perform(post("/submit/example") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(codeRequest))); + + perform.andExpect(status().isOk()); + } + +} \ No newline at end of file diff --git a/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java b/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java index 6becb169..e35b275d 100644 --- a/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java +++ b/src/test/java/kr/co/morandi/backend/docs/RestDocsSupport.java @@ -1,33 +1,33 @@ - package kr.co.morandi.backend.docs; +package kr.co.morandi.backend.docs; - import com.fasterxml.jackson.databind.ObjectMapper; - import com.fasterxml.jackson.databind.SerializationFeature; - import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; - import org.junit.jupiter.api.BeforeEach; - import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; - import org.springframework.restdocs.RestDocumentationContextProvider; - import org.springframework.restdocs.RestDocumentationExtension; - import org.springframework.test.web.servlet.MockMvc; - import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; - import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; - @ExtendWith(RestDocumentationExtension.class) - public abstract class RestDocsSupport { +@ExtendWith(RestDocumentationExtension.class) +public abstract class RestDocsSupport { - protected MockMvc mockMvc; - protected ObjectMapper objectMapper = new ObjectMapper() - .registerModule(new JavaTimeModule()) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + protected MockMvc mockMvc; + protected ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - @BeforeEach - void setUp(RestDocumentationContextProvider provider) { - this.mockMvc = MockMvcBuilders.standaloneSetup(initController()) - .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper)) - .apply(documentationConfiguration(provider)) - .build(); - } + @BeforeEach + void setUp(RestDocumentationContextProvider provider) { + this.mockMvc = MockMvcBuilders.standaloneSetup(initController()) + .setMessageConverters(new MappingJackson2HttpMessageConverter(objectMapper)) + .apply(documentationConfiguration(provider)) + .build(); + } - protected abstract Object initController(); - } \ No newline at end of file + protected abstract Object initController(); +} \ No newline at end of file