diff --git a/.github/workflows/deploy-prod.yaml b/.github/workflows/deploy-prod.yaml new file mode 100644 index 0000000..84c5d9d --- /dev/null +++ b/.github/workflows/deploy-prod.yaml @@ -0,0 +1,62 @@ +name: deploy on prod server + +on: + push: + branches: [test/main-cd] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - name: 체크아웃 + uses: actions/checkout@v4 + with: + submodules: true + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + + - name: 서브모듈 업데이트 + run: | + git submodule update --remote + + - name: JDK 11 설치 + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + cache: 'gradle' + + - name: Gradle에 실행 권한 부여 + run: chmod +x gradlew + + - name: 빌드 + run: ./gradlew build -x test + + - name: DockerHub 로그인 + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker 이미지 빌드 & DockerHub에 Push + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile-prod + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/qtudy-server:prod + + - name: EC2 서버에 배포 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.PROD_SERVER_HOST }} + username: ${{ secrets.PROD_SERVER_USERNAME }} + key: ${{ secrets.PROD_SERVER_PEM_KEY }} + envs: GITHUB_SHA + script: | + sudo docker stop $(sudo docker ps -a -q) + sudo docker rm $(sudo docker ps -a -q) + sudo docker pull ${{secrets.DOCKERHUB_USERNAME}}/qtudy-server:prod + sudo docker run -d -p 8080:8080 --name qtudy-server-container ${{secrets.DOCKERHUB_USERNAME}}/qtudy-server:prod + sudo docker image prune -f diff --git a/.github/workflows/deploy-stg.yaml b/.github/workflows/deploy-stg.yaml new file mode 100644 index 0000000..1b42132 --- /dev/null +++ b/.github/workflows/deploy-stg.yaml @@ -0,0 +1,62 @@ +name: deploy on stage server + +on: + push: + branches: [dev] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - name: 체크아웃 + uses: actions/checkout@v4 + with: + submodules: true + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + + - name: 서브모듈 업데이트 + run: | + git submodule update --remote + + - name: JDK 11 설치 + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + cache: 'gradle' + + - name: Gradle에 실행 권한 부여 + run: chmod +x gradlew + + - name: 빌드 + run: ./gradlew build -x test + + - name: DockerHub 로그인 + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker 이미지 빌드 & DockerHub에 Push + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile-stg + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/qtudy-server:latest + + - name: EC2 서버에 배포 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.STG_SERVER_HOST }} + username: ${{ secrets.STG_SERVER_USERNAME }} + key: ${{ secrets.STG_SERVER_PEM_KEY }} + envs: GITHUB_SHA + script: | + sudo docker stop $(sudo docker ps -a -q) + sudo docker rm $(sudo docker ps -a -q) + sudo docker pull ${{secrets.DOCKERHUB_USERNAME}}/qtudy-server + sudo docker run -d -p 8080:8080 --network="host" --name qtudy-server-container ${{secrets.DOCKERHUB_USERNAME}}/qtudy-server + sudo docker image prune -f diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..72b3537 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,38 @@ +name: run test + +on: + pull_request: + branches: [main, dev] + types: [opened, synchronize, reopened] + +permissions: + checks: write + pull-requests: write + +jobs: + test: + + runs-on: ubuntu-latest + + steps: + - name: 체크아웃 + uses: actions/checkout@v4 + + - name: JDK 11 설치 + uses: actions/setup-java@v4 + with: + distribution: 'corretto' + java-version: '11' + cache: 'gradle' + + - name: Gradle에 실행 권한 부여 + run: chmod +x gradlew + + - name: Test 실행 + run: ./gradlew clean test + + - name: Test 결과를 PR에 코멘트로 등록 + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: '**/build/test-results/test/TEST-*.xml' diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 0000000..5d47d73 --- /dev/null +++ b/appspec.yml @@ -0,0 +1,23 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ubuntu + overwrite: yes + +permissions: + - object: /home/ubuntu + pattern: "**" + owner: ubuntu + group: ubuntu + +hooks: + AfterInstall: + - location: scripts/shutdown-prev-application.sh + timeout: 120 + runas: ubuntu + + ApplicationStart: + - location: scripts/build-and-run-new-application.sh + timeout: 120 + runas: ubuntu diff --git a/build.gradle b/build.gradle index 48cc743..90dbf9e 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,9 @@ dependencies { testImplementation 'com.theokanning.openai-gpt3-java:service:0.18.2' testImplementation 'com.knuddels:jtokkit:1.0.0' testImplementation 'com.fasterxml.jackson.module:jackson-module-jsonSchema-jakarta:2.15.0' + + // Rest Assured + testImplementation 'io.rest-assured:rest-assured:4.5.1' } dependencyManagement { @@ -67,9 +70,16 @@ tasks.named('test') { } processResources.dependsOn('copySecret') +processResources.dependsOn('copySecretToBuild') tasks.register('copySecret', Copy) { from './BE_config' // 서브 모듈 디렉토리 경로 include "*.yml" // 설정 파일 복사 into 'src/main/resources' // 붙여넣을 위치 } + +tasks.register('copySecretToBuild', Copy) { + from './BE_config' // 서브 모듈 디렉토리 경로 + include "*.yml" // 설정 파일 복사 + into '$buildDir/resources/main' // 붙여넣을 위치 +} diff --git a/docker/Dockerfile-prod b/docker/Dockerfile-prod new file mode 100644 index 0000000..e70f220 --- /dev/null +++ b/docker/Dockerfile-prod @@ -0,0 +1,4 @@ +FROM amazoncorretto:11-alpine-jdk +EXPOSE 8080 +COPY ./build/libs/Qtudy-server-0.0.1-SNAPSHOT.jar /app.jar +CMD ["java", "-jar", "app.jar", "--spring.profiles.active=deploy"] diff --git a/docker/Dockerfile-stg b/docker/Dockerfile-stg new file mode 100644 index 0000000..455c6e8 --- /dev/null +++ b/docker/Dockerfile-stg @@ -0,0 +1,4 @@ +FROM amazoncorretto:11-alpine-jdk +EXPOSE 8080 +COPY ./build/libs/Qtudy-server-0.0.1-SNAPSHOT.jar /app.jar +CMD ["java", "-jar", "app.jar", "--spring.profiles.active=stage"] diff --git a/scripts/build-and-run-new-application.sh b/scripts/build-and-run-new-application.sh new file mode 100644 index 0000000..7315a41 --- /dev/null +++ b/scripts/build-and-run-new-application.sh @@ -0,0 +1,5 @@ +PROJECT_PATH=/home/ubuntu/Qtudy-BE +BUILD_PATH=$PROJECT_PATH/build/libs +BUILD_JAR=$BUILD_PATH/Qtudy-server-0.0.1-SNAPSHOT.jar + +nohup java -jar $BUILD_JAR --spring.profiles.active=stage > $PROJECT_PATH/nohup.out 2>&1 & diff --git a/scripts/shutdown-prev-application.sh b/scripts/shutdown-prev-application.sh new file mode 100644 index 0000000..1794c7b --- /dev/null +++ b/scripts/shutdown-prev-application.sh @@ -0,0 +1,7 @@ +JAVA_PID=`sudo lsof -i :8080 -t` +if [ -z $JAVA_PID ] + then echo "실행되고 있는 애플리케이션이 없습니다." +else + sudo kill -9 $JAVA_PID + echo "기존에 실행되고 있던 애플리케이션을 종료했습니다." +fi diff --git a/src/main/java/com/app/domain/categorizedproblem/dto/CategorizedProblemDto.java b/src/main/java/com/app/domain/categorizedproblem/dto/CategorizedProblemDto.java index 526a397..49042e8 100644 --- a/src/main/java/com/app/domain/categorizedproblem/dto/CategorizedProblemDto.java +++ b/src/main/java/com/app/domain/categorizedproblem/dto/CategorizedProblemDto.java @@ -20,6 +20,7 @@ public class CategorizedProblemDto { @Getter @Schema(name = "CategorizedProblemPost", description = "새 카테고리화 문제 생성 요청 DTO") + @AllArgsConstructor public static class Post { @Schema(description = "카테고리 ID 목록", example = "[1, 2]") @@ -31,6 +32,7 @@ public static class Post { @Getter @Schema(name = "CategorizedProblemPatch", description = "카테고리화 문제 수정 요청 DTO") + @AllArgsConstructor public static class Patch{ @Schema(description = "문제 이름", example = "수정된 문제 이름") private String problemName; diff --git a/src/main/java/com/app/domain/categorizedproblem/repository/CategorizedProblemRepository.java b/src/main/java/com/app/domain/categorizedproblem/repository/CategorizedProblemRepository.java index 0eff5b1..ec02490 100644 --- a/src/main/java/com/app/domain/categorizedproblem/repository/CategorizedProblemRepository.java +++ b/src/main/java/com/app/domain/categorizedproblem/repository/CategorizedProblemRepository.java @@ -1,18 +1,22 @@ package com.app.domain.categorizedproblem.repository; import com.app.domain.categorizedproblem.entity.CategorizedProblem; +import feign.Param; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; +import org.springframework.data.jpa.repository.Query; public interface CategorizedProblemRepository extends JpaRepository { boolean existsByCategoryCategoryIdAndProblemProblemId(Long categoryId, Long problemId); boolean existsByProblemProblemId(Long problemId); - Page findByCategoryCategoryId(Long categoryId, Pageable pageable); + @Query(value = "SELECT cp FROM CategorizedProblem cp JOIN FETCH cp.problem p JOIN FETCH cp.category c WHERE c.categoryId = :categoryId", + countQuery = "SELECT count(cp) FROM CategorizedProblem cp WHERE cp.category.categoryId = :categoryId") + Page findByCategoryCategoryId(@Param("categoryId") Long categoryId, Pageable pageable); List findByCategoryCategoryId(Long categoryId); } diff --git a/src/main/java/com/app/domain/categorizedproblem/service/CategorizedProblemService.java b/src/main/java/com/app/domain/categorizedproblem/service/CategorizedProblemService.java index 4dca70b..046843f 100644 --- a/src/main/java/com/app/domain/categorizedproblem/service/CategorizedProblemService.java +++ b/src/main/java/com/app/domain/categorizedproblem/service/CategorizedProblemService.java @@ -27,6 +27,7 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDType0Font; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -244,6 +245,7 @@ public CategorizedProblem updateCategorizedProblem(Long categorizedProblemId, Me } @Transactional(readOnly = true) + @Cacheable(value = "categorizedProblem", key = "#categoryId") public Page findCategorizedProblemsByCategoryId(Long categoryId, int page, int size) { PageRequest pageRequest = PageRequest.of(page, size); return categorizedProblemRepository.findByCategoryCategoryId(categoryId, pageRequest); diff --git a/src/main/java/com/app/domain/categorizedsummary/dto/CategorizedSummaryDto.java b/src/main/java/com/app/domain/categorizedsummary/dto/CategorizedSummaryDto.java index 0bdf94e..48c5b97 100644 --- a/src/main/java/com/app/domain/categorizedsummary/dto/CategorizedSummaryDto.java +++ b/src/main/java/com/app/domain/categorizedsummary/dto/CategorizedSummaryDto.java @@ -18,6 +18,7 @@ public class CategorizedSummaryDto { @Getter @Schema(name = "CategorizedSummaryPost", description = "새 카테고리화 요약 생성 요청 DTO") + @AllArgsConstructor public static class Post { @Schema(description = "카테고리 ID 목록", example = "[1, 2]") private List categoryIdList; diff --git a/src/main/java/com/app/domain/categorizedsummary/repository/CategorizedSummaryRepository.java b/src/main/java/com/app/domain/categorizedsummary/repository/CategorizedSummaryRepository.java index eadd6d8..ad59869 100644 --- a/src/main/java/com/app/domain/categorizedsummary/repository/CategorizedSummaryRepository.java +++ b/src/main/java/com/app/domain/categorizedsummary/repository/CategorizedSummaryRepository.java @@ -1,14 +1,18 @@ package com.app.domain.categorizedsummary.repository; import com.app.domain.categorizedsummary.entity.CategorizedSummary; +import feign.Param; 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; public interface CategorizedSummaryRepository extends JpaRepository { boolean existsByCategoryCategoryIdAndSummarySummaryId(Long categoryId, Long summaryId); boolean existsBySummarySummaryId(Long summaryId); - Page findByCategoryCategoryId(Long categoryId, Pageable pageable); + @Query(value = "SELECT cs FROM CategorizedSummary cs JOIN FETCH cs.summary s JOIN FETCH cs.category c WHERE c.categoryId = :categoryId", + countQuery = "SELECT count(cs) FROM CategorizedSummary cs WHERE cs.category.categoryId = :categoryId") + Page findByCategoryCategoryId(@Param("categoryId") Long categoryId, Pageable pageable); } diff --git a/src/main/java/com/app/domain/categorizedsummary/service/CategorizedSummaryService.java b/src/main/java/com/app/domain/categorizedsummary/service/CategorizedSummaryService.java index 346a7d0..890f57e 100644 --- a/src/main/java/com/app/domain/categorizedsummary/service/CategorizedSummaryService.java +++ b/src/main/java/com/app/domain/categorizedsummary/service/CategorizedSummaryService.java @@ -17,6 +17,7 @@ import java.io.IOException; import javax.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -104,6 +105,8 @@ public CategorizedSummary findVerifiedCategorizedSummaryByCategorizedSummaryId(L .orElseThrow(() -> new EntityNotFoundException(ErrorCode.CATEGORIZED_SUMMARY_NOT_EXISTS)); } + @Transactional(readOnly = true) + @Cacheable(value = "categorizedSummary", key = "#categoryId") public Page findCategorziedSummarysByCategoryId(Long categoryId, int page, int size) { PageRequest pageRequest = PageRequest.of(page, size); return categorizedSummaryRepository.findByCategoryCategoryId(categoryId, pageRequest); diff --git a/src/main/java/com/app/domain/category/dto/CategoryDto.java b/src/main/java/com/app/domain/category/dto/CategoryDto.java index b0ce1ba..ac99ef6 100644 --- a/src/main/java/com/app/domain/category/dto/CategoryDto.java +++ b/src/main/java/com/app/domain/category/dto/CategoryDto.java @@ -22,6 +22,7 @@ public class CategoryDto { @Getter + @AllArgsConstructor @Schema(name = "CategoryRequestDto", description = "카테고리 요청 DTO") public static class RequestDto { @NotBlank diff --git a/src/main/java/com/app/domain/category/entity/Category.java b/src/main/java/com/app/domain/category/entity/Category.java index 0195bac..72e9ad6 100644 --- a/src/main/java/com/app/domain/category/entity/Category.java +++ b/src/main/java/com/app/domain/category/entity/Category.java @@ -33,10 +33,10 @@ public class Category extends BaseEntity { @Column(nullable = false, length = 10) private CategoryType categoryType; - @OneToMany(mappedBy = "category") + @OneToMany(mappedBy = "category", fetch = FetchType.LAZY) private List categorizedSummaries; - @OneToMany(mappedBy = "category") + @OneToMany(mappedBy = "category", fetch = FetchType.LAZY) private List categorizedProblems; public void updateMember(Member member) { diff --git a/src/main/java/com/app/domain/file/entity/File.java b/src/main/java/com/app/domain/file/entity/File.java index 2734142..618b920 100644 --- a/src/main/java/com/app/domain/file/entity/File.java +++ b/src/main/java/com/app/domain/file/entity/File.java @@ -32,7 +32,7 @@ public abstract class File extends BaseEntity { //@Column(name = "MEMBER_ID") //추후에 Members 엔티티와 연결 //private String memberId; @JsonIgnore - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "MEMBER_ID") private Member member; diff --git a/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/AiGeneratedProblem.java b/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/AiGeneratedProblem.java index 799c280..dbd8c9d 100644 --- a/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/AiGeneratedProblem.java +++ b/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/AiGeneratedProblem.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import lombok.AllArgsConstructor; @@ -20,7 +21,7 @@ public class AiGeneratedProblem extends Problem { @JsonIgnore - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "FILE_ID") private ProblemFile problemFile; diff --git a/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/ProblemFile.java b/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/ProblemFile.java index 4024453..8aa2f66 100644 --- a/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/ProblemFile.java +++ b/src/main/java/com/app/domain/problem/aigeneratedproblem/entity/ProblemFile.java @@ -20,7 +20,7 @@ public class ProblemFile extends File { - @OneToMany(mappedBy = "problemFile", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "problemFile", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) private List aiQuestions; diff --git a/src/main/java/com/app/domain/problem/membersavedproblem/dto/MemberSavedProblemDto.java b/src/main/java/com/app/domain/problem/membersavedproblem/dto/MemberSavedProblemDto.java index 9081f19..9896bc0 100644 --- a/src/main/java/com/app/domain/problem/membersavedproblem/dto/MemberSavedProblemDto.java +++ b/src/main/java/com/app/domain/problem/membersavedproblem/dto/MemberSavedProblemDto.java @@ -13,6 +13,7 @@ public class MemberSavedProblemDto { @Getter @Schema(name = "MemberSavedProblemPost", description = "새 문제 생성 요청 DTO") + @AllArgsConstructor public static class Post { @NotBlank @@ -34,6 +35,7 @@ public static class Post { @Getter @Schema(name = "MemberSavedProblemPatch", description = "문제 수정 요청 DTO") + @AllArgsConstructor public static class Patch { @Schema(description = "문제의 이름", example = "수정된 문제 이름") private String problemName; diff --git a/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/AiGeneratedSummary.java b/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/AiGeneratedSummary.java index f9a469d..af3682f 100644 --- a/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/AiGeneratedSummary.java +++ b/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/AiGeneratedSummary.java @@ -4,6 +4,7 @@ import com.app.domain.summary.entity.Summary; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import lombok.AllArgsConstructor; @@ -21,7 +22,7 @@ @Entity public class AiGeneratedSummary extends Summary { - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "FILE_ID") private SummaryFile summaryFile; } diff --git a/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/SummaryFile.java b/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/SummaryFile.java index 3098004..9145e51 100644 --- a/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/SummaryFile.java +++ b/src/main/java/com/app/domain/summary/aigeneratedsummary/entity/SummaryFile.java @@ -21,7 +21,7 @@ @DiscriminatorValue("SUMMARY") public class SummaryFile extends File { - @OneToMany(mappedBy = "summaryFile", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(mappedBy = "summaryFile", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) private List aiQuestions; @Column(name = "SUMMARY_AMOUNT", nullable = false) diff --git a/src/main/java/com/app/domain/summary/dto/SummaryDto.java b/src/main/java/com/app/domain/summary/dto/SummaryDto.java index 002b4e7..a0feb3e 100644 --- a/src/main/java/com/app/domain/summary/dto/SummaryDto.java +++ b/src/main/java/com/app/domain/summary/dto/SummaryDto.java @@ -8,6 +8,7 @@ public class SummaryDto { @Getter @Schema(name = "SummaryPatchDto", description = "요약 수정 요청 DTO") + @AllArgsConstructor public static class Patch { @Schema(description = "요약 제목", example = "새로운 요약 제목") private String summaryTitle; diff --git a/src/main/java/com/app/domain/summary/membersavedsummary/dto/MemberSavedSummaryDto.java b/src/main/java/com/app/domain/summary/membersavedsummary/dto/MemberSavedSummaryDto.java index b1c4b4f..55d6a88 100644 --- a/src/main/java/com/app/domain/summary/membersavedsummary/dto/MemberSavedSummaryDto.java +++ b/src/main/java/com/app/domain/summary/membersavedsummary/dto/MemberSavedSummaryDto.java @@ -11,6 +11,7 @@ public class MemberSavedSummaryDto { @Getter @Schema(name = "MemberSavedSummaryPostDto", description = "새 요약 생성 요청 DTO") + @AllArgsConstructor public static class Post { @NotBlank(message = "제목을 입력해주세요") @Schema(description = "요약 제목", example = "요약 제목") @@ -23,6 +24,7 @@ public static class Post { @Getter @Schema(name = "MemberSavedSummaryPatchDto", description = "요약 수정 요청 DTO") + @AllArgsConstructor public static class Patch { @Schema(description = "요약 제목", example = "새로운 요약 제목") private String summaryTitle; diff --git a/src/main/java/com/app/global/config/CacheConfig.java b/src/main/java/com/app/global/config/CacheConfig.java new file mode 100644 index 0000000..5cb5822 --- /dev/null +++ b/src/main/java/com/app/global/config/CacheConfig.java @@ -0,0 +1,18 @@ +package com.app.global.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +public class CacheConfig extends CachingConfigurerSupport { + @Bean + @Override + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager("categorizedProblem", "categorizedSummary"); + } +} diff --git a/src/main/java/com/app/global/config/web/WebConfig.java b/src/main/java/com/app/global/config/web/WebConfig.java index b2f67cd..de1e4a7 100644 --- a/src/main/java/com/app/global/config/web/WebConfig.java +++ b/src/main/java/com/app/global/config/web/WebConfig.java @@ -49,14 +49,15 @@ public void addInterceptors(InterceptorRegistry registry) { "/oauth/kakao/callback", "/api/problem/getFileProblems", "/api/summary/getSummary", - "/api/member-saved-problem/{memberSavedProblemId}", - "/api/member-saved-summary/{memberSavedSummaryID}", + "/api/member-saved-problem/{problemId}", + "/api/member-saved-summary/{summaryId}", "/api/category/list", "/api/category/{categoryId}", "/api/categorized-problem/{categorizedProblemId}", "/api/categorized-summary/{categorizedSummaryID}", "/api/problem/getFileProblems/{fileId}", - "/api/summary/getSummary/{fileId}" + "/api/summary/getSummary/{fileId}", + "/api/fake/**" ); // 인증 인터셉터를 동작시키지 않을 예외적인 uri 작성 registry.addInterceptor(adminAuthorizationInterceptor) //인증 인터셉터 다음 인가 인터셉터 실행 @@ -65,4 +66,4 @@ public void addInterceptors(InterceptorRegistry registry) { } -} \ No newline at end of file +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ef46c2a..d74c444 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,3 @@ spring: profiles: - active: local \ No newline at end of file + active: local diff --git a/src/main/resources/templates/loginForm.html b/src/main/resources/templates/loginForm.html index 561b7dd..e9a6770 100644 --- a/src/main/resources/templates/loginForm.html +++ b/src/main/resources/templates/loginForm.html @@ -25,7 +25,7 @@
- +
카카오 로그인
diff --git a/src/test/java/com/app/gpt/GPTResponseFormatTest.java b/src/test/java/com/app/gpt/GPTResponseFormatTest.java deleted file mode 100644 index 2451e79..0000000 --- a/src/test/java/com/app/gpt/GPTResponseFormatTest.java +++ /dev/null @@ -1,220 +0,0 @@ -package com.app.gpt; - -import com.app.domain.problem.aigeneratedproblem.service.ProblemFileService; -import com.app.gpt.dto.ProblemSchema; -import com.app.gpt.dto.SummarySchema; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema; -import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.SchemaFactoryWrapper; -import com.knuddels.jtokkit.Encodings; -import com.knuddels.jtokkit.api.Encoding; -import com.knuddels.jtokkit.api.ModelType; -import com.theokanning.openai.completion.chat.ChatCompletionRequest; -import com.theokanning.openai.completion.chat.ChatCompletionRequest.ChatCompletionRequestFunctionCall; -import com.theokanning.openai.completion.chat.ChatFunction; -import com.theokanning.openai.completion.chat.ChatMessage; -import com.theokanning.openai.completion.chat.ChatMessageRole; -import com.theokanning.openai.service.FunctionExecutor; -import com.theokanning.openai.service.OpenAiService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.multipart.MultipartFile; -import org.yaml.snakeyaml.Yaml; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static com.app.gpt.dto.ProblemSchema.ProblemChoices; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) -@SuppressWarnings("NonAsciiCharacters") -public class GPTResponseFormatTest { - private static final Encoding ENCODING = Encodings.newDefaultEncodingRegistry().getEncodingForModel(ModelType.GPT_3_5_TURBO); - private static final String GPT_3_5_TURBO_0125 = "gpt-3.5-turbo-0125"; - private static final String APPLICATION_LOCAL_YML = "src/main/resources/application-local.yml"; - - private static final FunctionExecutor PROBLEM_FUNCTION_EXECUTOR = new FunctionExecutor(List.of(ChatFunction.builder() - .name("get_new_quiz") - .description("요청한 내용을 기반으로 객관식 문제 만들어줘 빈칸이 있으면 안돼") - .executor(ProblemSchema.class, parameter -> parameter) - .build())); - - private static final String DIFFICULTY = "어려운"; - private static final int MAX_TOKEN_COUNT = 550; - - private static final String TEST_PDF_FILE_PATH = "src/test/resources/pdf/"; - private static final String TEST_TEXT_FILE_PATH = "src/test/resources/text/"; - - private OpenAiService openAiService; - private ObjectMapper objectMapper; - - @BeforeEach - void setUp() throws FileNotFoundException { - Map properties = new Yaml().load(new FileReader(APPLICATION_LOCAL_YML)); - String openAiApiToken = properties.get("openai-api-token"); - openAiService = new OpenAiService(openAiApiToken); - - objectMapper = new ObjectMapper(); - } - -// @RepeatedTest(5) - @Test - void 텍스트_기반_AI_문제_생성() throws IOException { - String testFileName = "text1.text"; - String text = Files.readString(Paths.get(TEST_TEXT_FILE_PATH + testFileName)); - List messages = new ArrayList<>(); - messages.add(new ChatMessage(ChatMessageRole.SYSTEM.value(), "당신이 시험 문제 출제자가 됐다고 가정하겠습니다.")); - - List textChunks = splitToken(text); - for (String textChunk : textChunks) { - String prompt = String.format("%s의 내용을 기반으로 %s 객관식 문제 만들어줘\"", textChunk, DIFFICULTY); - messages.add(new ChatMessage(ChatMessageRole.USER.value(), prompt)); - - ChatMessage responseMessage = createChatCompletion(PROBLEM_FUNCTION_EXECUTOR, messages); - JsonNode arguments = responseMessage.getFunctionCall().getArguments(); - - System.out.println(arguments.toPrettyString()); - assertProblemSchema(arguments); - } - } - -// @RepeatedTest(5) - @Test - void PDF_기반_AI_문제_생성() throws IOException { - String testFileName = "pdf1.pdf"; - String text = readPdfFile(testFileName); - List messages = new ArrayList<>(); - messages.add(new ChatMessage(ChatMessageRole.SYSTEM.value(), "당신이 시험 문제 출제자가 됐다고 가정하겠습니다.")); - - List textChunks = splitToken(text); - for (String textChunk : textChunks) { - String prompt = String.format("%s의 내용을 기반으로 %s 객관식 문제 만들어줘", textChunk, DIFFICULTY); - messages.add(new ChatMessage(ChatMessageRole.USER.value(), prompt)); - - ChatMessage responseMessage = createChatCompletion(PROBLEM_FUNCTION_EXECUTOR, messages); - JsonNode arguments = responseMessage.getFunctionCall().getArguments(); - - System.out.println(arguments.toPrettyString()); - assertProblemSchema(arguments); - } - } - - private List splitToken(String text) { - List tokens = new ArrayList<>(); - - StringBuilder currentText = new StringBuilder(); - for (char character : text.toCharArray()) { - currentText.append(character); - if (ENCODING.countTokens(currentText.toString()) >= MAX_TOKEN_COUNT) { - tokens.add(currentText.toString()); - currentText.setLength(0); - } - } - tokens.add(currentText.toString()); - return tokens; - } - - private void assertProblemSchema(JsonNode arguments) throws JsonProcessingException { - assertDoesNotThrow(() -> objectMapper.treeToValue(arguments, ProblemSchema.class), "예상치 못한 값이 Json 데이터에 포함되어 있습니다."); - ProblemSchema problemSchema = objectMapper.treeToValue(arguments, ProblemSchema.class); - ProblemChoices problemChoices = problemSchema.getProblemChoices(); - assertAll( - () -> assertThat(problemSchema.getProblemName()).isNotNull(), - () -> assertThat(problemChoices.getOne()).isNotNull(), - () -> assertThat(problemChoices.getTwo()).isNotNull(), - () -> assertThat(problemChoices.getThree()).isNotNull(), - () -> assertThat(problemChoices.getFour()).isNotNull(), - () -> assertThat(problemSchema.getProblemAnswer()).isNotNull(), - () -> assertThat(problemSchema.getProblemCommentary()).isNotNull() - ); - } - -// @RepeatedTest(5) - @Test - void PDF_기반_AI_요약정리_생성() throws IOException { - FunctionExecutor functionExecutor = new FunctionExecutor(List.of(ChatFunction.builder() - .name("get_new_summary") - .description("요청한 내용을 기반으로 요점 정리해줘") - .executor(SummarySchema.class, parameter -> parameter) - .build())); - - String testFileName = "pdf1.pdf"; - String text = readPdfFile(testFileName); - List messages = new ArrayList<>(); - messages.add(new ChatMessage(ChatMessageRole.SYSTEM.value(), "당신은 강의 자료의 핵심 정보를 요약해주는 역할입니다.")); - - List textChunks = splitToken(text); - for (String textChunk : textChunks) { - String prompt = String.format("%s의 내용을 기반으로 요점 정리 해줘", textChunk); - messages.add(new ChatMessage(ChatMessageRole.USER.value(), prompt)); - - ChatMessage responseMessage = createChatCompletion(functionExecutor, messages); - JsonNode arguments = responseMessage.getFunctionCall().getArguments(); - - System.out.println(arguments.toPrettyString()); - assertDoesNotThrow(() -> objectMapper.treeToValue(arguments, SummarySchema.class), "예상치 못한 값이 Json 데이터에 포함되어 있습니다."); - SummarySchema summarySchema = objectMapper.treeToValue(arguments, SummarySchema.class); - assertThat(summarySchema).isNotNull(); - } - } - - private String readPdfFile(String testFileName) throws IOException { - MultipartFile multipartFile = new MockMultipartFile("test.pdf", new FileInputStream(TEST_PDF_FILE_PATH + testFileName)); - return ProblemFileService.convertFileToString(multipartFile); - } - - private ChatMessage createChatCompletion(FunctionExecutor functionExecutor, List messages) { - ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder() - .model(GPT_3_5_TURBO_0125) - .messages(messages) - .functions(functionExecutor.getFunctions()) - .functionCall(ChatCompletionRequestFunctionCall.of("auto")) - .maxTokens(1_024) - .build(); - - return openAiService.createChatCompletion(chatCompletionRequest).getChoices().get(0).getMessage(); - } - - @Test - void 스키마를_JSON으로_변환하여_출력한다() throws JsonProcessingException { - // 메타데이터가 포함되지 않은 JSON 출력 - ProblemSchema fp = new ProblemSchema( - "문제명", - new ProblemChoices( - "1번 선지", - "2번 선지", - "3번 선지", - "4번 선지" - ), - "문제의 답", - "문제 해설" - ); - String normal = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(fp); - System.out.println(normal); - - System.out.println("-----------"); - // 메타데이터가 포함된 JSON 출력 - SchemaFactoryWrapper visitor = new SchemaFactoryWrapper(); - objectMapper.acceptJsonFormatVisitor(ProblemSchema.class, visitor); - JsonSchema jsonSchema = visitor.finalSchema(); - ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); - String json = objectWriter.writeValueAsString(jsonSchema); - System.out.println(json); - } -} diff --git a/src/test/java/com/app/gpt/dto/ProblemSchema.java b/src/test/java/com/app/gpt/dto/ProblemSchema.java deleted file mode 100644 index a465b70..0000000 --- a/src/test/java/com/app/gpt/dto/ProblemSchema.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.app.gpt.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -public class ProblemSchema { - @JsonPropertyDescription("의문문 형식의 문제명") - @JsonProperty(required = true) - String problemName; - - @JsonPropertyDescription("option의 갯수는 무조건 4개이어야 해") - @JsonProperty(required = true) - ProblemChoices problemChoices; - - @JsonPropertyDescription("선지에서 하나의 정답만 골라줘") - @JsonProperty(required = true) - String problemAnswer; - - @JsonPropertyDescription("문제의 정답에 대한 해설을 알려줘") - @JsonProperty(required = true) - String problemCommentary; - - @Getter - @NoArgsConstructor - @AllArgsConstructor - public static class ProblemChoices { - @JsonPropertyDescription("The OPTION A of the question.") - @JsonProperty(required = true) - String one; - - @JsonPropertyDescription("The OPTION B of the question.") - @JsonProperty(required = true) - String two; - - @JsonPropertyDescription("The OPTION C of the question.") - @JsonProperty(required = true) - String three; - - @JsonPropertyDescription("The OPTION D of the question.") - @JsonProperty(required = true) - String four; - } -} diff --git a/src/test/java/com/app/gpt/dto/SummarySchema.java b/src/test/java/com/app/gpt/dto/SummarySchema.java deleted file mode 100644 index 9af5989..0000000 --- a/src/test/java/com/app/gpt/dto/SummarySchema.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.app.gpt.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -public class SummarySchema { - - @JsonPropertyDescription("요점정리 내용") - @JsonProperty(required = true) - String summaryContent; -} diff --git a/src/test/java/com/app/integration/category/CategoryIntegrationTest.java b/src/test/java/com/app/integration/category/CategoryIntegrationTest.java new file mode 100644 index 0000000..02ef5f7 --- /dev/null +++ b/src/test/java/com/app/integration/category/CategoryIntegrationTest.java @@ -0,0 +1,172 @@ +package com.app.integration.category; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.app.domain.category.dto.CategoryDto; +import com.app.domain.category.dto.CategoryDto.RequestDto; +import com.app.domain.category.contsant.CategoryType; +import com.app.integration.dto.FakeSignUpRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class CategoryIntegrationTest { + + @LocalServerPort + int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Nested + @DisplayName("카테고리 관리 테스트") + class CategoryManagementTests { + + @Test + @DisplayName("카테고리 생성") + void 카테고리를_생성한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + // when + ExtractableResponse createResponse = categoryCreateRequest("카테고리1", CategoryType.PROBLEM, jwtToken); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategoryDto.Response body = createResponse.as(CategoryDto.Response.class); + assertThat(body.getCategoryName()).isEqualTo("카테고리1"); + assertThat(body.getCategoryType()).isEqualTo(CategoryType.PROBLEM); + } + + @Test + @DisplayName("카테고리 생성 실패 - 중복 이름") + void 중복된_이름으로_카테고리_생성_실패() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + categoryCreateRequest("카테고리중복", CategoryType.PROBLEM, jwtToken); // First time + + // when + ExtractableResponse createResponse = categoryCreateRequest("카테고리중복", CategoryType.PROBLEM, jwtToken); // Second time + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + @Test + @DisplayName("카테고리 수정") + void 카테고리를_수정한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long categoryId = createCategoryAndGetId("카테고리3", CategoryType.PROBLEM, jwtToken); + + // when + ExtractableResponse updateResponse = categoryUpdateRequest(categoryId, "수정된 카테고리", CategoryType.PROBLEM, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategoryDto.Response body = updateResponse.as(CategoryDto.Response.class); + assertThat(body.getCategoryName()).isEqualTo("수정된 카테고리"); + } + + @Test + @DisplayName("존재하지 않는 카테고리 수정 실패") + void 존재하지_않는_카테고리를_수정_시도() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long nonExistentCategoryId = 9999L; // Assuming this ID doesn't exist + + // when + ExtractableResponse updateResponse = categoryUpdateRequest(nonExistentCategoryId, "없는 카테고리", CategoryType.PROBLEM, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + + @Test + @DisplayName("카테고리 삭제") + void 카테고리를_삭제한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long categoryId = createCategoryAndGetId("카테고리4", CategoryType.PROBLEM, jwtToken); + + // when + ExtractableResponse deleteResponse = categoryDeleteRequest(categoryId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + @DisplayName("존재하지 않는 카테고리 삭제 실패") + void 존재하지_않는_카테고리를_삭제_시도() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long nonExistentCategoryId = 9999L; // Assuming this ID doesn't exist + + // when + ExtractableResponse deleteResponse = categoryDeleteRequest(nonExistentCategoryId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } + + private Long createCategoryAndGetId(String categoryName, CategoryType categoryType, String jwtToken) { + ExtractableResponse response = categoryCreateRequest(categoryName, categoryType, jwtToken); + return response.body().jsonPath().getLong("categoryId"); + } + + private ExtractableResponse categoryCreateRequest(String categoryName, CategoryType categoryType, String jwtToken) { + RequestDto categoryCreateRequest = new RequestDto(categoryName, categoryType); + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(categoryCreateRequest) + .when().post("/api/category/new") + .then().log().all() + .extract(); + } + + private ExtractableResponse categoryUpdateRequest(Long categoryId, String categoryName, CategoryType categoryType, String jwtToken) { + RequestDto categoryUpdateRequest = new RequestDto(categoryName, categoryType); + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(categoryUpdateRequest) + .when().patch("/api/category/edit/" + categoryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse categoryDeleteRequest(Long categoryId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().delete("/api/category/delete/" + categoryId) + .then().log().all() + .extract(); + } +} diff --git a/src/test/java/com/app/integration/dto/FakeSignUpRequest.java b/src/test/java/com/app/integration/dto/FakeSignUpRequest.java new file mode 100644 index 0000000..26fc84e --- /dev/null +++ b/src/test/java/com/app/integration/dto/FakeSignUpRequest.java @@ -0,0 +1,17 @@ +package com.app.integration.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FakeSignUpRequest { + + private String nickname; + private String email; + + public FakeSignUpRequest(String nickname, String email) { + this.nickname = nickname; + this.email = email; + } +} diff --git a/src/test/java/com/app/integration/fake/FakeSignUpController.java b/src/test/java/com/app/integration/fake/FakeSignUpController.java new file mode 100644 index 0000000..6e5003c --- /dev/null +++ b/src/test/java/com/app/integration/fake/FakeSignUpController.java @@ -0,0 +1,40 @@ +package com.app.integration.fake; + +import com.app.domain.member.constant.MemberType; +import com.app.domain.member.constant.Role; +import com.app.domain.member.entity.Member; +import com.app.domain.member.service.MemberService; +import com.app.global.jwt.dto.JwtTokenDto; +import com.app.global.jwt.service.TokenManager; +import com.app.integration.dto.FakeSignUpRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Profile; +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; + +@RequiredArgsConstructor +@Profile(value = {"local", "test"}) +@RestController +public class FakeSignUpController { + + private final MemberService memberService; + private final TokenManager tokenManager; + + @PostMapping("/api/fake/login") + public ResponseEntity fakeSignUp(@RequestBody FakeSignUpRequest request) { + Member member = memberService.findMemberByEmail(request.getEmail()).orElseGet( + () -> memberService.registerMember(Member.builder() + .nickName(request.getNickname()) + .email(request.getEmail()) + .memberType(MemberType.KAKAO) + .role(Role.USER) + .build()) + ); + + JwtTokenDto jwtTokenDto = tokenManager.createJwtTokenDto(member.getMemberId(), member.getRole()); + member.updateRefreshToken(jwtTokenDto); + return ResponseEntity.ok(jwtTokenDto.getAccessToken()); + } +} diff --git a/src/test/java/com/app/integration/file/FileIntegrationTest.java b/src/test/java/com/app/integration/file/FileIntegrationTest.java new file mode 100644 index 0000000..e388ad6 --- /dev/null +++ b/src/test/java/com/app/integration/file/FileIntegrationTest.java @@ -0,0 +1,176 @@ +package com.app.integration.file; + +import com.app.domain.file.dto.Request.DownloadPdfRequestDto; +import com.app.domain.file.dto.Request.DuplicateFileNameRequestDto; +import com.app.domain.file.dto.Request.UpdateFileRequestDto; +import com.app.domain.file.dto.Response.DownloadPdfResponseDto; +import com.app.domain.file.dto.Response.DuplicateFileNameResponseDto; +import com.app.domain.file.entity.File; +import com.app.domain.file.repository.FileRepository; +import com.app.domain.problem.aigeneratedproblem.entity.ProblemFile; +import com.app.domain.problem.aigeneratedproblem.repository.ProblemFileRepository; +import com.app.domain.summary.aigeneratedsummary.entity.SummaryFile; +import com.app.domain.summary.aigeneratedsummary.repository.SummaryFileRepository; +import com.app.global.config.ENUM.PdfType; +import com.app.integration.dto.FakeSignUpRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +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.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import java.util.Optional; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class FileIntegrationTest { + + @LocalServerPort + int port; + + @MockBean + private FileRepository fileRepository; + @MockBean + private ProblemFileRepository problemFileRepository; + @MockBean + private SummaryFileRepository summaryFileRepository; + + @BeforeEach + void setUp() { + RestAssured.port = port; + mockFileRepositoryData(); + } + + private void mockFileRepositoryData() { + // 테스트용 파일 생성 + + File mockProblemFile = new ProblemFile(); + mockProblemFile.setFileId(1L); + mockProblemFile.setFileName("문제 파일"); + + File mockSummaryFile = new SummaryFile(); + mockSummaryFile.setFileId(1L); + mockSummaryFile.setFileName("요점 정리 파일"); + + // 파일 Repository 모킹 + Mockito.when(fileRepository.findById(Mockito.anyLong())).thenReturn(Optional.of(mockProblemFile)); + Mockito.when(fileRepository.findByFileId(Mockito.anyLong())).thenReturn(Optional.of(mockProblemFile)); + Mockito.when(fileRepository.save(Mockito.any(File.class))).thenAnswer(invocation -> invocation.getArgument(0)); + Mockito.when(fileRepository.findByFileNameAndDtype(Mockito.anyString(), Mockito.anyString())).thenReturn(Optional.empty()); + + // 문제 파일 Repository 모킹 + Mockito.when(problemFileRepository.findByFileId(Mockito.anyLong())).thenReturn(Optional.of((ProblemFile) mockProblemFile)); + + // 요점 정리 파일 Repository 모킹 + Mockito.when(summaryFileRepository.findByFileId(Mockito.anyLong())).thenReturn(Optional.of((SummaryFile) mockSummaryFile)); + } + + @Test + @DisplayName("파일 이름 업데이트") + void 파일_이름_업데이트_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long fileId = 1L; + UpdateFileRequestDto updateFileRequestDto = new UpdateFileRequestDto("새 파일 이름"); + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(updateFileRequestDto) + .when().patch("/api/file/updateFile/" + fileId) + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertEquals("Sucess", response.asString()); + } + + @Test + @DisplayName("PDF 문제 다운로드") + void PDF_문제_다운로드_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long fileId = 1L; + DownloadPdfRequestDto downloadPdfRequestDto = new DownloadPdfRequestDto(PdfType.SUMMARY); + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(downloadPdfRequestDto) + .when().post("/api/file/downloadPdf/" + fileId) + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + DownloadPdfResponseDto responseDto = response.as(DownloadPdfResponseDto.class); + assertNotNull(responseDto.getFileUrl()); + } + + @Test + @DisplayName("파일 삭제") + void 파일_삭제_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long fileId = 1L; + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().delete("/api/file/deleteFile/" + fileId) + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertEquals("Sucess", response.asString()); + } + + @Test + @DisplayName("파일 이름 중복 확인") + void 파일_이름_중복_확인_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + DuplicateFileNameRequestDto duplicateFileNameRequestDto = new DuplicateFileNameRequestDto("문제 파일", "SUMMARY"); + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(duplicateFileNameRequestDto) + .when().post("/api/file/check-duplicate") + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + DuplicateFileNameResponseDto responseDto = response.as(DuplicateFileNameResponseDto.class); + assertNotNull(responseDto.getDuplicate()); + + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } +} diff --git a/src/test/java/com/app/integration/problem/aigeneratedproblem/AiGeneratedProblemIntegrationTest.java b/src/test/java/com/app/integration/problem/aigeneratedproblem/AiGeneratedProblemIntegrationTest.java new file mode 100644 index 0000000..3ce5711 --- /dev/null +++ b/src/test/java/com/app/integration/problem/aigeneratedproblem/AiGeneratedProblemIntegrationTest.java @@ -0,0 +1,101 @@ +package com.app.integration.problem.aigeneratedproblem; + +import com.app.domain.problem.aigeneratedproblem.entity.AiGeneratedProblem; +import com.app.domain.problem.aigeneratedproblem.entity.ProblemFile; +import com.app.domain.problem.aigeneratedproblem.repository.AiGeneratedProblemRepository; +import com.app.domain.problem.aigeneratedproblem.repository.ProblemFileRepository; +import com.app.domain.problem.aigeneratedproblem.service.AiGeneratedProblemService; +import com.app.global.config.ENUM.ProblemType; +import com.app.integration.dto.FakeSignUpRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +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.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class AiGeneratedProblemIntegrationTest { + + @LocalServerPort + int port; + + @MockBean + private ProblemFileRepository problemFileRepository; + + @MockBean + private AiGeneratedProblemRepository aiGeneratedProblemRepository; + + @MockBean + private AiGeneratedProblemService aiGeneratedProblemService; + + @BeforeEach + void setUp() { + RestAssured.port = port; + + // Mocking된 ProblemFile 객체 정의 + ProblemFile mockProblemFile = new ProblemFile(); + mockProblemFile.setFileId(1L); // 필요한 경우 ID 설정 + + // Mocking된 AiGeneratedProblem 리스트 정의 + List mockProblems = Arrays.asList( + AiGeneratedProblem.builder() + .problemName("Sample Problem 1") + .problemAnswer("Answer 1") + .problemCommentary("Commentary 1") + .problemType(ProblemType.MULTIPLE) + .problemChoices(Arrays.asList("Choice 1", "Choice 2")) + .problemFile(mockProblemFile) + .build() + ); + + // Mocking된 ProblemFileRepository와 AiGeneratedProblemRepository의 동작 정의 + Mockito.when(problemFileRepository.getByFileId(Mockito.anyLong())).thenReturn(mockProblemFile); + Mockito.when(aiGeneratedProblemRepository.findByProblemFile_FileId(Mockito.anyLong())).thenReturn(mockProblems); + + // checkIsWriter 메서드 Mocking + Mockito.when(aiGeneratedProblemService.checkIsWriter(Mockito.any(HttpServletRequest.class), Mockito.anyLong())).thenReturn(true); + } + + @Test + @DisplayName("사용자가 생성한 AI 문제의 문제 리스트를 조회한다.") + void AI_문제_리스트_조회하기_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + long fileId = 1L; + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().get("/api/problem/getFileProblems/" + fileId) + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } +} diff --git a/src/test/java/com/app/integration/problem/categorizedproblem/CategorizedProblemIntegrationTest.java b/src/test/java/com/app/integration/problem/categorizedproblem/CategorizedProblemIntegrationTest.java new file mode 100644 index 0000000..9a156a5 --- /dev/null +++ b/src/test/java/com/app/integration/problem/categorizedproblem/CategorizedProblemIntegrationTest.java @@ -0,0 +1,344 @@ +package com.app.integration.problem.categorizedproblem; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.app.domain.category.contsant.CategoryType; +import com.app.domain.category.dto.CategoryDto.RequestDto; +import com.app.global.config.ENUM.ProblemType; +import com.app.integration.dto.FakeSignUpRequest; +import com.app.domain.categorizedproblem.dto.CategorizedProblemDto; +import com.app.domain.problem.membersavedproblem.dto.MemberSavedProblemDto; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class CategorizedProblemIntegrationTest { + + @LocalServerPort + int port; + + private static Long categoryId; + private static String jwtToken; + + @BeforeAll + static void setUp(@LocalServerPort int port) { + RestAssured.port = port; + jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + categoryId = createCategory(jwtToken); + } + + @Nested + @DisplayName("Categorized Problem 생성 테스트") + class CreateCategorizedProblemTests { + + @Test + @DisplayName("정상적으로 카테고리화 문제를 생성한다") + void 카테고리화_문제를_생성한다() { + // given + Long problemId = createProblem(jwtToken); + + // when + ExtractableResponse createResponse = createCategorizedProblemRequest(categoryId, problemId, jwtToken); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategorizedProblemDto.PostResponse response = createResponse.as(CategorizedProblemDto.PostResponse.class); + assertThat(response.getCategoryId()).containsExactly(categoryId); + assertThat(response.getProblemId()).isEqualTo(problemId); + } + + @Test + @DisplayName("잘못된 입력으로 카테고리화 문제 생성 실패") + void 잘못된_입력으로_카테고리화_문제_생성_실패() { + // given + Long invalidProblemId = 999L; + + // when + CategorizedProblemDto.Post postDto = new CategorizedProblemDto.Post( + List.of(categoryId), + invalidProblemId + ); + + ExtractableResponse createResponse = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/categorized-problem/new") + .then().log().all() + .extract(); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Problem 수정 테스트") + class UpdateCategorizedProblemTests { + + @Test + @DisplayName("정상적으로 카테고리화 문제를 수정한다") + void 카테고리화_문제를_수정한다() { + // given + Long problemId = createProblem(jwtToken); + ExtractableResponse createResponse = createCategorizedProblemRequest(categoryId, problemId, jwtToken); + Long categorizedProblemId = createResponse.as(CategorizedProblemDto.PostResponse.class).getCategorizedProblemId().get(0); + + // when + ExtractableResponse updateResponse = updateCategorizedProblemRequest(categorizedProblemId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategorizedProblemDto.Response response = updateResponse.as(CategorizedProblemDto.Response.class); + assertThat(response.getProblemName()).isEqualTo("수정된 문제 이름"); + assertThat(response.getProblemAnswer()).isEqualTo("수정된 정답"); + assertThat(response.getProblemCommentary()).isEqualTo("수정된 해설"); + assertThat(response.getProblemChoices()).containsExactly("수정된 옵션 A", "수정된 옵션 B"); + } + + @Test + @DisplayName("존재하지 않는 카테고리화 문제를 수정 시도") + void 존재하지_않는_카테고리화_문제를_수정_시도() { + // given + Long nonExistentCategorizedProblemId = 999L; + + // when + ExtractableResponse updateResponse = updateCategorizedProblemRequest(nonExistentCategorizedProblemId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Problem 삭제 테스트") + class DeleteCategorizedProblemTests { + + @Test + @DisplayName("정상적으로 카테고리화 문제를 삭제한다") + void 카테고리화_문제를_삭제한다() { + // given + Long problemId = createProblem(jwtToken); + ExtractableResponse createResponse = createCategorizedProblemRequest(categoryId, problemId, jwtToken); + Long categorizedProblemId = createResponse.as(CategorizedProblemDto.PostResponse.class).getCategorizedProblemId().get(0); + + // when + ExtractableResponse deleteResponse = deleteCategorizedProblemRequest(categorizedProblemId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + @DisplayName("존재하지 않는 카테고리화 문제를 삭제 시도") + void 존재하지_않는_카테고리화_문제를_삭제_시도() { + // given + Long nonExistentCategorizedProblemId = 999L; + + // when + ExtractableResponse deleteResponse = deleteCategorizedProblemRequest(nonExistentCategorizedProblemId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Problem 조회 테스트") + class GetCategorizedProblemTests { + + @Test + @DisplayName("정상적으로 카테고리화 문제를 조회한다") + void 카테고리화_문제를_조회한다() { + // given + Long problemId = createProblem(jwtToken); + ExtractableResponse createResponse = createCategorizedProblemRequest(categoryId, problemId, jwtToken); + Long categorizedProblemId = createResponse.as(CategorizedProblemDto.PostResponse.class).getCategorizedProblemId().get(0); + + // when + ExtractableResponse getResponse = getCategorizedProblemRequest(categorizedProblemId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategorizedProblemDto.LinkedSharedResponse response = getResponse.as(CategorizedProblemDto.LinkedSharedResponse.class); + assertThat(response.getResponse().getProblemName()).isEqualTo("문제 이름"); + assertThat(response.getResponse().getProblemAnswer()).isEqualTo("정답"); + assertThat(response.getResponse().getProblemCommentary()).isEqualTo("해설"); + assertThat(response.getResponse().getProblemType()).isEqualTo(ProblemType.MULTIPLE); + assertThat(response.getResponse().getProblemChoices()).containsExactly("옵션 A", "옵션 B"); + } + + @Test + @DisplayName("존재하지 않는 카테고리화 문제를 조회 시도") + void 존재하지_않는_카테고리화_문제를_조회_시도() { + // given + Long nonExistentCategorizedProblemId = 999L; + + // when + ExtractableResponse getResponse = getCategorizedProblemRequest(nonExistentCategorizedProblemId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Problem PDF 생성 및 다운로드 테스트") + class PdfTests { + + @Test + @DisplayName("정상적으로 문제 PDF를 다운로드한다") + void 문제_PDF_다운로드() { + // given + + // when + ExtractableResponse pdfResponse = downloadProblemPdfRequest(categoryId, jwtToken); + + // then + assertThat(pdfResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(pdfResponse.header(HttpHeaders.CONTENT_TYPE)).isEqualTo(MediaType.APPLICATION_PDF_VALUE); + assertThat(pdfResponse.header(HttpHeaders.CONTENT_DISPOSITION)).contains("attachment"); + } + + @Test + @DisplayName("정상적으로 정답 PDF를 다운로드한다") + void 정답_PDF_다운로드() { + // given + + // when + ExtractableResponse pdfResponse = downloadAnswerPdfRequest(categoryId, jwtToken); + + // then + assertThat(pdfResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(pdfResponse.header(HttpHeaders.CONTENT_TYPE)).isEqualTo(MediaType.APPLICATION_PDF_VALUE); + assertThat(pdfResponse.header(HttpHeaders.CONTENT_DISPOSITION)).contains("attachment"); + } + } + + private ExtractableResponse createCategorizedProblemRequest(Long categoryId, Long problemId, String jwtToken) { + CategorizedProblemDto.Post postDto = new CategorizedProblemDto.Post( + List.of(categoryId), + problemId + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/categorized-problem/new") + .then().log().all() + .extract(); + } + + private ExtractableResponse updateCategorizedProblemRequest(Long categorizedProblemId, String jwtToken) { + MemberSavedProblemDto.Patch patchDto = new MemberSavedProblemDto.Patch( + "수정된 문제 이름", + "수정된 정답", + "수정된 해설", + List.of("수정된 옵션 A", "수정된 옵션 B") + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(patchDto) + .when().patch("/api/categorized-problem/edit/" + categorizedProblemId) + .then().log().all() + .extract(); + } + + private ExtractableResponse deleteCategorizedProblemRequest(Long categorizedProblemId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().delete("/api/categorized-problem/delete/" + categorizedProblemId) + .then().log().all() + .extract(); + } + + private ExtractableResponse getCategorizedProblemRequest(Long categorizedProblemId) { + return RestAssured.given().log().all() + .when().get("/api/categorized-problem/" + categorizedProblemId) + .then().log().all() + .extract(); + } + + private ExtractableResponse downloadProblemPdfRequest(Long categoryId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().post("/api/categorized-problem/download-problem-pdf/" + categoryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse downloadAnswerPdfRequest(Long categoryId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().post("/api/categorized-problem/download-answer-pdf/" + categoryId) + .then().log().all() + .extract(); + } + + private static String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } + + private static Long createCategory(String jwtToken) { + String categoryName = "카테고리 이름"; + CategoryType categoryType = CategoryType.PROBLEM; + ExtractableResponse response = categoryCreateRequest(categoryName, categoryType, jwtToken); + return response.jsonPath().getLong("categoryId"); + } + + private static Long createProblem(String jwtToken) { + MemberSavedProblemDto.Post postDto = new MemberSavedProblemDto.Post( + "문제 이름", + "정답", + "해설", + ProblemType.MULTIPLE, + List.of("옵션 A", "옵션 B") + ); + + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/member-saved-problem/new") + .then().log().all() + .extract(); + + return response.jsonPath().getLong("problemId"); + } + + private static ExtractableResponse categoryCreateRequest(String categoryName, CategoryType categoryType, String jwtToken) { + RequestDto categoryCreateRequest = new RequestDto(categoryName, categoryType); + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(categoryCreateRequest) + .when().post("/api/category/new") + .then().log().all() + .extract(); + } +} diff --git a/src/test/java/com/app/integration/problem/membersavedproblem/MemberSavedProblemIntegrationTest.java b/src/test/java/com/app/integration/problem/membersavedproblem/MemberSavedProblemIntegrationTest.java new file mode 100644 index 0000000..3b5011b --- /dev/null +++ b/src/test/java/com/app/integration/problem/membersavedproblem/MemberSavedProblemIntegrationTest.java @@ -0,0 +1,258 @@ +package com.app.integration.problem.membersavedproblem; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.app.global.config.ENUM.ProblemType; +import com.app.integration.dto.FakeSignUpRequest; +import com.app.domain.problem.membersavedproblem.dto.MemberSavedProblemDto; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class MemberSavedProblemIntegrationTest { + + @LocalServerPort + int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Nested + @DisplayName("문제 생성 테스트") + class CreateProblemTests { + + @Test + @DisplayName("정상적으로 문제를 생성한다") + void 문제를_생성한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + // when + ExtractableResponse createResponse = createProblemRequest(jwtToken); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + MemberSavedProblemDto.Response response = createResponse.as(MemberSavedProblemDto.Response.class); + assertThat(response.getProblemName()).isEqualTo("문제 이름"); + assertThat(response.getProblemAnswer()).isEqualTo("정답"); + assertThat(response.getProblemCommentary()).isEqualTo("해설"); + assertThat(response.getProblemType()).isEqualTo(ProblemType.MULTIPLE); + assertThat(response.getProblemChoices()).containsExactly("옵션 A", "옵션 B"); + } + + @Test + @DisplayName("잘못된 입력으로 문제 생성 실패") + void 잘못된_입력으로_문제_생성_실패() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + // when + MemberSavedProblemDto.Post postDto = new MemberSavedProblemDto.Post( + "", // 문제 이름이 비어있음 + "정답", + "해설", + ProblemType.MULTIPLE, + List.of("옵션 A", "옵션 B") + ); + + ExtractableResponse createResponse = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/member-saved-problem/new") + .then().log().all() + .extract(); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("문제 수정 테스트") + class UpdateProblemTests { + + @Test + @DisplayName("정상적으로 문제를 수정한다") + void 문제를_수정한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + ExtractableResponse createResponse = createProblemRequest(jwtToken); + Long problemId = createResponse.as(MemberSavedProblemDto.Response.class).getProblemId(); + + // when + ExtractableResponse updateResponse = updateProblemRequest(problemId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + MemberSavedProblemDto.Response response = updateResponse.as(MemberSavedProblemDto.Response.class); + assertThat(response.getProblemName()).isEqualTo("수정된 문제 이름"); + assertThat(response.getProblemAnswer()).isEqualTo("수정된 정답"); + assertThat(response.getProblemCommentary()).isEqualTo("수정된 해설"); + assertThat(response.getProblemChoices()).containsExactly("수정된 옵션 A", "수정된 옵션 B"); + } + + @Test + @DisplayName("존재하지 않는 문제를 수정 시도") + void 존재하지_않는_문제를_수정_시도() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long nonExistentProblemId = 999L; + + // when + ExtractableResponse updateResponse = updateProblemRequest(nonExistentProblemId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("문제 삭제 테스트") + class DeleteProblemTests { + + @Test + @DisplayName("정상적으로 문제를 삭제한다") + void 문제를_삭제한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + ExtractableResponse createResponse = createProblemRequest(jwtToken); + Long problemId = createResponse.as(MemberSavedProblemDto.Response.class).getProblemId(); + + // when + ExtractableResponse deleteResponse = deleteProblemRequest(problemId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + @DisplayName("존재하지 않는 문제를 삭제 시도") + void 존재하지_않는_문제를_삭제_시도() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long nonExistentProblemId = 999L; + + // when + ExtractableResponse deleteResponse = deleteProblemRequest(nonExistentProblemId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("문제 조회 테스트") + class GetProblemTests { + + @Test + @DisplayName("정상적으로 문제를 조회한다") + void 문제를_조회한다() { + // given + ExtractableResponse createResponse = createProblemRequest(memberSignUpRequest("닉네임", "email@test.com")); + Long problemId = createResponse.as(MemberSavedProblemDto.Response.class).getProblemId(); + + // when + ExtractableResponse getResponse = getProblemRequest(problemId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + MemberSavedProblemDto.LinkSharedResponse response = getResponse.as(MemberSavedProblemDto.LinkSharedResponse.class); + assertThat(response.getResponse().getProblemName()).isEqualTo("문제 이름"); + assertThat(response.getResponse().getProblemAnswer()).isEqualTo("정답"); + assertThat(response.getResponse().getProblemCommentary()).isEqualTo("해설"); + assertThat(response.getResponse().getProblemType()).isEqualTo(ProblemType.MULTIPLE); + assertThat(response.getResponse().getProblemChoices()).containsExactly("옵션 A", "옵션 B"); + } + + @Test + @DisplayName("존재하지 않는 문제를 조회 시도") + void 존재하지_않는_문제를_조회_시도() { + // given + Long nonExistentProblemId = 999L; + + // when + ExtractableResponse getResponse = getProblemRequest(nonExistentProblemId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + private ExtractableResponse createProblemRequest(String jwtToken) { + MemberSavedProblemDto.Post postDto = new MemberSavedProblemDto.Post( + "문제 이름", + "정답", + "해설", + ProblemType.MULTIPLE, + List.of("옵션 A", "옵션 B") + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/member-saved-problem/new") + .then().log().all() + .extract(); + } + + private ExtractableResponse updateProblemRequest(Long problemId, String jwtToken) { + MemberSavedProblemDto.Patch patchDto = new MemberSavedProblemDto.Patch( + "수정된 문제 이름", + "수정된 정답", + "수정된 해설", + List.of("수정된 옵션 A", "수정된 옵션 B") + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(patchDto) + .when().patch("/api/member-saved-problem/edit/" + problemId) + .then().log().all() + .extract(); + } + + private ExtractableResponse deleteProblemRequest(Long problemId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().delete("/api/member-saved-problem/delete/" + problemId) + .then().log().all() + .extract(); + } + + private ExtractableResponse getProblemRequest(Long problemId) { + return RestAssured.given().log().all() + .when().get("/api/member-saved-problem/" + problemId) + .then().log().all() + .extract(); + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } +} diff --git a/src/test/java/com/app/integration/problemfile/ProblemFileIntegrationTest.java b/src/test/java/com/app/integration/problemfile/ProblemFileIntegrationTest.java new file mode 100644 index 0000000..aa32936 --- /dev/null +++ b/src/test/java/com/app/integration/problemfile/ProblemFileIntegrationTest.java @@ -0,0 +1,201 @@ +package com.app.integration.problemfile; + +import com.app.domain.problem.aigeneratedproblem.dto.ProblemFile.Request.AiGenerateProblemDto; +import com.app.global.config.ENUM.Amount; +import com.app.global.config.ENUM.ProblemDifficulty; +import com.app.global.config.ENUM.ProblemType; +import com.app.global.config.S3.S3Service; +import com.app.integration.dto.FakeSignUpRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ProblemFileIntegrationTest { + + @LocalServerPort + int port; + + @MockBean + private S3Service s3Service; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Test + @DisplayName("정상적으로 텍스트기반 AI 문제를 생성한다.") + void 텍스트기반_문제파일_생성_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + AiGenerateProblemDto aiGenerateProblemDto = new AiGenerateProblemDto( + "텍스트", + ProblemType.MULTIPLE, + Amount.FEW, + ProblemDifficulty.EASY, + "파일 이름"); + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(aiGenerateProblemDto) + .when().post("/api/problemFile/generateProblemFileByText") + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertNotNull(response.jsonPath().getLong("fileId")); + assertNotNull(response.jsonPath().getString("problems")); + } + + @Test + @DisplayName("정상적으로 이미지기반 AI 문제를 생성한다.") + void 이미지기반_문제파일_생성_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + AiGenerateProblemDto aiGenerateProblemDto = new AiGenerateProblemDto( + "텍스트", + ProblemType.MULTIPLE, + Amount.FEW, + ProblemDifficulty.EASY, + "파일 이름"); + + // 테스트 이미지 파일 읽기 + ClassPathResource imageResource = new ClassPathResource("image/testImage.png"); + MockMultipartFile mockFile; + try { + mockFile = new MockMultipartFile("file", imageResource.getFilename(), + MediaType.IMAGE_JPEG_VALUE, imageResource.getInputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // When + ExtractableResponse response; + try { + response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .multiPart("file", mockFile.getOriginalFilename(), mockFile.getBytes(), mockFile.getContentType()) + .formParam("type", aiGenerateProblemDto.getType()) + .formParam("text", aiGenerateProblemDto.getText()) + .formParam("difficulty", aiGenerateProblemDto.getDifficulty().toString()) + .formParam("amount", aiGenerateProblemDto.getAmount().toString()) + .formParam("fileName", aiGenerateProblemDto.getFileName()) + .when().post("/api/problemFile/generateProblemFileByImage") + .then().log().all() + .extract(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertNotNull(response.jsonPath().getLong("fileId")); + assertNotNull(response.jsonPath().getString("problems")); + } + + @Test + @DisplayName("정상적으로 PDF기반 AI 문제를 생성한다.") + void PDF기반_문제파일_생성_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + AiGenerateProblemDto aiGenerateProblemDto = new AiGenerateProblemDto( + "텍스트", + ProblemType.MULTIPLE, + Amount.FEW, + ProblemDifficulty.EASY, + "PDF 파일 이름"); + + // 테스트 PDF 파일 읽기 + ClassPathResource pdfResource = new ClassPathResource("pdf/pdf1.pdf"); + MockMultipartFile mockFile; + try { + mockFile = new MockMultipartFile("file", pdfResource.getFilename(), + MediaType.APPLICATION_PDF_VALUE, pdfResource.getInputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // When + ExtractableResponse response; + try { + response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .multiPart("file", mockFile.getOriginalFilename(), mockFile.getBytes(), mockFile.getContentType()) + .formParam("type", aiGenerateProblemDto.getType()) + .formParam("text", aiGenerateProblemDto.getText()) + .formParam("difficulty", aiGenerateProblemDto.getDifficulty().toString()) + .formParam("amount", aiGenerateProblemDto.getAmount().toString()) + .formParam("fileName", aiGenerateProblemDto.getFileName()) + .when().post("/api/problemFile/generateProblemFileByPdf") + .then().log().all() + .extract(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertNotNull(response.jsonPath().getLong("fileId")); + assertNotNull(response.jsonPath().getString("problems")); + } + + @Test + @DisplayName("정상적으로 사용자가 생성한 모든 AI 파일을 조회한다.") + void 모든_AI_문제파일_조회하기_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + int pageNumber = 1; + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().get("/api/problemFile/searchAiProblemFileList/" + pageNumber) + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + // 응답 본문에 따른 추가 확인 사항을 여기에 작성하세요 + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } +} diff --git a/src/test/java/com/app/integration/summary/aigeneratedsummary/AiGeneratedSummaryIntegrationTest.java b/src/test/java/com/app/integration/summary/aigeneratedsummary/AiGeneratedSummaryIntegrationTest.java new file mode 100644 index 0000000..20526a5 --- /dev/null +++ b/src/test/java/com/app/integration/summary/aigeneratedsummary/AiGeneratedSummaryIntegrationTest.java @@ -0,0 +1,90 @@ +package com.app.integration.summary.aigeneratedsummary; +import com.app.domain.summary.aigeneratedsummary.dto.Summary.Response.SummaryResponseDto; +import com.app.domain.summary.aigeneratedsummary.entity.AiGeneratedSummary; +import com.app.domain.summary.aigeneratedsummary.entity.SummaryFile; +import com.app.domain.summary.aigeneratedsummary.service.AiGeneratedSummaryService; +import com.app.integration.dto.FakeSignUpRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +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.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import javax.servlet.http.HttpServletRequest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class AiGeneratedSummaryIntegrationTest { + + @LocalServerPort + int port; + + @MockBean + private AiGeneratedSummaryService aiGeneratedSummaryService; + + @BeforeEach + void setUp() { + RestAssured.port = port; + + // Mocking된 SummaryFile 객체 정의 + SummaryFile mockSummaryFile = new SummaryFile(); + mockSummaryFile.setFileId(1L); // 필요한 경우 ID 설정 + + // Mocking된 AiGeneratedSummary 객체 정의 + AiGeneratedSummary mockSummary = AiGeneratedSummary.builder() + .summaryId(1L) + .summaryTitle("Sample Summary Title") + .summaryContent("This is a sample summary content.") + .summaryFile(mockSummaryFile) + .build(); + + // Mocking된 AiGeneratedSummaryService의 동작 정의 + Mockito.when(aiGeneratedSummaryService.GetSummary(Mockito.anyLong())).thenReturn(mockSummary); + Mockito.when(aiGeneratedSummaryService.checkIsWriter(Mockito.any(HttpServletRequest.class), Mockito.anyLong())).thenReturn(true); + } + + @Test + @DisplayName("사용자가 생성한 AI 요약을 조회한다.") + void AI_요약_조회하기_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + long fileId = 1L; + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().get("/api/summary/getSummary/" + fileId) + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + SummaryResponseDto responseDto = response.as(SummaryResponseDto.class); + assertEquals("Sample Summary Title", responseDto.getResponse().getSummaryTitle()); + assertEquals("This is a sample summary content.", responseDto.getResponse().getSummaryContent()); + assertTrue(responseDto.getIsWriter()); + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } +} + diff --git a/src/test/java/com/app/integration/summary/categorizedsummary/CategorizedSummaryIntegrationTest.java b/src/test/java/com/app/integration/summary/categorizedsummary/CategorizedSummaryIntegrationTest.java new file mode 100644 index 0000000..194caea --- /dev/null +++ b/src/test/java/com/app/integration/summary/categorizedsummary/CategorizedSummaryIntegrationTest.java @@ -0,0 +1,318 @@ +package com.app.integration.summary.categorizedsummary; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.app.domain.category.contsant.CategoryType; +import com.app.domain.category.dto.CategoryDto.RequestDto; +import com.app.domain.summary.membersavedsummary.dto.MemberSavedSummaryDto; +import com.app.global.config.ENUM.ProblemType; +import com.app.integration.dto.FakeSignUpRequest; +import com.app.domain.categorizedsummary.dto.CategorizedSummaryDto; +import com.app.domain.summary.dto.SummaryDto; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +public class CategorizedSummaryIntegrationTest { + + @LocalServerPort + int port; + + private static Long categoryId; + private static String jwtToken; + + @BeforeAll + static void setUp(@LocalServerPort int port) { + RestAssured.port = port; + jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + categoryId = createCategory(jwtToken); + } + + @Nested + @DisplayName("Categorized Summary 생성 테스트") + class CreateCategorizedSummaryTests { + + @Test + @DisplayName("정상적으로 카테고리화 요약을 생성한다") + void 카테고리화_요약을_생성한다() { + // given + Long summaryId = createSummary(jwtToken); + + // when + ExtractableResponse createResponse = createCategorizedSummaryRequest(categoryId, summaryId, jwtToken); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategorizedSummaryDto.PostResponse response = createResponse.as(CategorizedSummaryDto.PostResponse.class); + assertThat(response.getCategoryId()).containsExactly(categoryId); + assertThat(response.getSummaryId()).isEqualTo(summaryId); + } + + @Test + @DisplayName("잘못된 입력으로 카테고리화 요약 생성 실패") + void 잘못된_입력으로_카테고리화_요약_생성_실패() { + // given + Long invalidSummaryId = 999L; + + // when + CategorizedSummaryDto.Post postDto = new CategorizedSummaryDto.Post( + List.of(categoryId), + invalidSummaryId + ); + + ExtractableResponse createResponse = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/categorized-summary/new") + .then().log().all() + .extract(); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Summary 수정 테스트") + class UpdateCategorizedSummaryTests { + + @Test + @DisplayName("정상적으로 카테고리화 요약을 수정한다") + void 카테고리화_요약을_수정한다() { + // given + Long summaryId = createSummary(jwtToken); + ExtractableResponse createResponse = createCategorizedSummaryRequest(categoryId, summaryId, jwtToken); + Long categorizedSummaryId = createResponse.as(CategorizedSummaryDto.PostResponse.class).getCategorizedSummaryId().get(0); + + // when + ExtractableResponse updateResponse = updateCategorizedSummaryRequest(categorizedSummaryId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategorizedSummaryDto.Response response = updateResponse.as(CategorizedSummaryDto.Response.class); + assertThat(response.getSummaryTitle()).isEqualTo("수정된 요약 제목"); + assertThat(response.getSummaryContent()).isEqualTo("수정된 요약 내용"); + } + + @Test + @DisplayName("존재하지 않는 카테고리화 요약을 수정 시도") + void 존재하지_않는_카테고리화_요약을_수정_시도() { + // given + Long nonExistentCategorizedSummaryId = 999L; + + // when + ExtractableResponse updateResponse = updateCategorizedSummaryRequest(nonExistentCategorizedSummaryId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Summary 삭제 테스트") + class DeleteCategorizedSummaryTests { + + @Test + @DisplayName("정상적으로 카테고리화 요약을 삭제한다") + void 카테고리화_요약을_삭제한다() { + // given + Long summaryId = createSummary(jwtToken); + ExtractableResponse createResponse = createCategorizedSummaryRequest(categoryId, summaryId, jwtToken); + Long categorizedSummaryId = createResponse.as(CategorizedSummaryDto.PostResponse.class).getCategorizedSummaryId().get(0); + + // when + ExtractableResponse deleteResponse = deleteCategorizedSummaryRequest(categorizedSummaryId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + @DisplayName("존재하지 않는 카테고리화 요약을 삭제 시도") + void 존재하지_않는_카테고리화_요약을_삭제_시도() { + // given + Long nonExistentCategorizedSummaryId = 999L; + + // when + ExtractableResponse deleteResponse = deleteCategorizedSummaryRequest(nonExistentCategorizedSummaryId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Summary 조회 테스트") + class GetCategorizedSummaryTests { + + @Test + @DisplayName("정상적으로 카테고리화 요약을 조회한다") + void 카테고리화_요약을_조회한다() { + // given + Long summaryId = createSummary(jwtToken); + ExtractableResponse createResponse = createCategorizedSummaryRequest(categoryId, summaryId, jwtToken); + Long categorizedSummaryId = createResponse.as(CategorizedSummaryDto.PostResponse.class).getCategorizedSummaryId().get(0); + + // when + ExtractableResponse getResponse = getCategorizedSummaryRequest(categorizedSummaryId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + CategorizedSummaryDto.LinkedSharedResponse response = getResponse.as(CategorizedSummaryDto.LinkedSharedResponse.class); + assertThat(response.getResponse().getSummaryTitle()).isEqualTo("요약 제목"); + assertThat(response.getResponse().getSummaryContent()).isEqualTo("요약 내용"); + } + + @Test + @DisplayName("존재하지 않는 카테고리화 요약을 조회 시도") + void 존재하지_않는_카테고리화_요약을_조회_시도() { + // given + Long nonExistentCategorizedSummaryId = 999L; + + // when + ExtractableResponse getResponse = getCategorizedSummaryRequest(nonExistentCategorizedSummaryId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("Categorized Summary PDF 생성 및 다운로드 테스트") + class PdfTests { + + @Test + @DisplayName("정상적으로 요약 PDF를 다운로드한다") + void 요약_PDF_다운로드() { + // given + Long summaryId = createSummary(jwtToken); + ExtractableResponse createResponse = createCategorizedSummaryRequest(categoryId, summaryId, jwtToken); + Long categorizedSummaryId = createResponse.as(CategorizedSummaryDto.PostResponse.class).getCategorizedSummaryId().get(0); + + // when + ExtractableResponse pdfResponse = downloadSummaryPdfRequest(categorizedSummaryId, jwtToken); + + // then + assertThat(pdfResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(pdfResponse.header(HttpHeaders.CONTENT_TYPE)).isEqualTo(MediaType.APPLICATION_PDF_VALUE); + assertThat(pdfResponse.header(HttpHeaders.CONTENT_DISPOSITION)).contains("attachment"); + } + } + + private ExtractableResponse createCategorizedSummaryRequest(Long categoryId, Long summaryId, String jwtToken) { + CategorizedSummaryDto.Post postDto = new CategorizedSummaryDto.Post( + List.of(categoryId), + summaryId + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/categorized-summary/new") + .then().log().all() + .extract(); + } + + private ExtractableResponse updateCategorizedSummaryRequest(Long categorizedSummaryId, String jwtToken) { + SummaryDto.Patch patchDto = new SummaryDto.Patch( + "수정된 요약 제목", + "수정된 요약 내용" + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(patchDto) + .when().patch("/api/categorized-summary/edit/" + categorizedSummaryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse deleteCategorizedSummaryRequest(Long categorizedSummaryId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().delete("/api/categorized-summary/delete/" + categorizedSummaryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse getCategorizedSummaryRequest(Long categorizedSummaryId) { + return RestAssured.given().log().all() + .when().get("/api/categorized-summary/" + categorizedSummaryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse downloadSummaryPdfRequest(Long categorizedSummaryId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().post("/api/categorized-summary/download-pdf/" + categorizedSummaryId) + .then().log().all() + .extract(); + } + + private static String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } + + private static Long createCategory(String jwtToken) { + String categoryName = "카테고리 이름"; + CategoryType categoryType = CategoryType.SUMMARY; + ExtractableResponse response = categoryCreateRequest(categoryName, categoryType, jwtToken); + return response.jsonPath().getLong("categoryId"); + } + + private static Long createSummary(String jwtToken) { + MemberSavedSummaryDto.Post postDto = new MemberSavedSummaryDto.Post( + "요약 제목", + "요약 내용" + ); + + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/member-saved-summary/new") + .then().log().all() + .extract(); + + return response.jsonPath().getLong("summaryId"); + } + + + + private static ExtractableResponse categoryCreateRequest(String categoryName, CategoryType categoryType, String jwtToken) { + RequestDto categoryCreateRequest = new RequestDto(categoryName, categoryType); + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(categoryCreateRequest) + .when().post("/api/category/new") + .then().log().all() + .extract(); + } +} diff --git a/src/test/java/com/app/integration/summary/membersavedsummary/MemberSavedSummaryIntegrationTest.java b/src/test/java/com/app/integration/summary/membersavedsummary/MemberSavedSummaryIntegrationTest.java new file mode 100644 index 0000000..6d367d8 --- /dev/null +++ b/src/test/java/com/app/integration/summary/membersavedsummary/MemberSavedSummaryIntegrationTest.java @@ -0,0 +1,279 @@ +package com.app.integration.summary.membersavedsummary; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.app.integration.dto.FakeSignUpRequest; +import com.app.domain.summary.membersavedsummary.dto.MemberSavedSummaryDto; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.io.IOException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) + +public class MemberSavedSummaryIntegrationTest { + + @LocalServerPort + int port; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Nested + @DisplayName("요약 생성 테스트") + class CreateSummaryTests { + + @Test + @DisplayName("정상적으로 요약을 생성한다") + void 요약을_생성한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + // when + ExtractableResponse createResponse = createSummaryRequest(jwtToken); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + MemberSavedSummaryDto.Response response = createResponse.as(MemberSavedSummaryDto.Response.class); + assertThat(response.getSummaryTitle()).isEqualTo("요약 제목"); + assertThat(response.getSummaryContent()).isEqualTo("요약 내용"); + } + + @Test + @DisplayName("잘못된 입력으로 요약 생성 실패") + void 잘못된_입력으로_요약_생성_실패() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + + // when + MemberSavedSummaryDto.Post postDto = new MemberSavedSummaryDto.Post( + "", // 요약 제목이 비어있음 + "요약 내용" + ); + + ExtractableResponse createResponse = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/member-saved-summary/new") + .then().log().all() + .extract(); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("요약 수정 테스트") + class UpdateSummaryTests { + + @Test + @DisplayName("정상적으로 요약을 수정한다") + void 요약을_수정한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + ExtractableResponse createResponse = createSummaryRequest(jwtToken); + Long summaryId = createResponse.as(MemberSavedSummaryDto.Response.class).getSummaryId(); + + // when + ExtractableResponse updateResponse = updateSummaryRequest(summaryId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + MemberSavedSummaryDto.Response response = updateResponse.as(MemberSavedSummaryDto.Response.class); + assertThat(response.getSummaryTitle()).isEqualTo("새로운 요약 제목"); + assertThat(response.getSummaryContent()).isEqualTo("새로운 요약 내용"); + } + + @Test + @DisplayName("존재하지 않는 요약을 수정 시도") + void 존재하지_않는_요약을_수정_시도() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long nonExistentSummaryId = 999L; + + // when + ExtractableResponse updateResponse = updateSummaryRequest(nonExistentSummaryId, jwtToken); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("요약 삭제 테스트") + class DeleteSummaryTests { + + @Test + @DisplayName("정상적으로 요약을 삭제한다") + void 요약을_삭제한다() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + ExtractableResponse createResponse = createSummaryRequest(jwtToken); + Long summaryId = createResponse.as(MemberSavedSummaryDto.Response.class).getSummaryId(); + + // when + ExtractableResponse deleteResponse = deleteSummaryRequest(summaryId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + @Test + @DisplayName("존재하지 않는 요약을 삭제 시도") + void 존재하지_않는_요약을_삭제_시도() { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + Long nonExistentSummaryId = 999L; + + // when + ExtractableResponse deleteResponse = deleteSummaryRequest(nonExistentSummaryId, jwtToken); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("요약 조회 테스트") + class GetSummaryTests { + + @Test + @DisplayName("정상적으로 요약을 조회한다") + void 요약을_조회한다() { + // given + ExtractableResponse createResponse = createSummaryRequest(memberSignUpRequest("닉네임", "email@test.com")); + Long summaryId = createResponse.as(MemberSavedSummaryDto.Response.class).getSummaryId(); + + // when + ExtractableResponse getResponse = getSummaryRequest(summaryId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + MemberSavedSummaryDto.LinkedSharedResponse response = getResponse.as(MemberSavedSummaryDto.LinkedSharedResponse.class); + assertThat(response.getResponse().getSummaryTitle()).isEqualTo("요약 제목"); + assertThat(response.getResponse().getSummaryContent()).isEqualTo("요약 내용"); + } + + @Test + @DisplayName("존재하지 않는 요약을 조회 시도") + void 존재하지_않는_요약을_조회_시도() { + // given + Long nonExistentSummaryId = 999L; + + // when + ExtractableResponse getResponse = getSummaryRequest(nonExistentSummaryId); + + // then + assertThat(getResponse.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value()); + } + } + + @Nested + @DisplayName("요약 PDF 다운로드 테스트") + class DownloadSummaryPdfTests { + + @Test + @DisplayName("정상적으로 요약 PDF를 다운로드한다") + void 요약_PDF를_다운로드한다() throws IOException { + // given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + ExtractableResponse createResponse = createSummaryRequest(jwtToken); + Long summaryId = createResponse.as(MemberSavedSummaryDto.Response.class).getSummaryId(); + + // when + ExtractableResponse downloadResponse = downloadSummaryPdfRequest(summaryId, jwtToken); + + // then + assertThat(downloadResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(downloadResponse.header(HttpHeaders.CONTENT_TYPE)).isEqualTo(MediaType.APPLICATION_PDF_VALUE); + assertThat(downloadResponse.header(HttpHeaders.CONTENT_DISPOSITION)).contains("attachment; filename="); + } + } + + private ExtractableResponse createSummaryRequest(String jwtToken) { + MemberSavedSummaryDto.Post postDto = new MemberSavedSummaryDto.Post( + "요약 제목", + "요약 내용" + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(postDto) + .when().post("/api/member-saved-summary/new") + .then().log().all() + .extract(); + } + + private ExtractableResponse updateSummaryRequest(Long summaryId, String jwtToken) { + MemberSavedSummaryDto.Patch patchDto = new MemberSavedSummaryDto.Patch( + "새로운 요약 제목", + "새로운 요약 내용" + ); + + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(patchDto) + .when().patch("/api/member-saved-summary/edit/" + summaryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse deleteSummaryRequest(Long summaryId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().delete("/api/member-saved-summary/delete/" + summaryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse getSummaryRequest(Long summaryId) { + return RestAssured.given().log().all() + .when().get("/api/member-saved-summary/" + summaryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse downloadSummaryPdfRequest(Long summaryId, String jwtToken) { + return RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().post("/api/member-saved-summary/download-pdf/" + summaryId) + .then().log().all() + .extract(); + } + + private ExtractableResponse downloadSummaryPdfRequestWithoutAuth(Long summaryId) { + return RestAssured.given().log().all() + .when().post("/api/member-saved-summary/download-pdf/" + summaryId) + .then().log().all() + .extract(); + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } +} diff --git a/src/test/java/com/app/integration/summaryfile/aigeneratedsummary/SummaryFileIntegrationTest.java b/src/test/java/com/app/integration/summaryfile/aigeneratedsummary/SummaryFileIntegrationTest.java new file mode 100644 index 0000000..4397bbb --- /dev/null +++ b/src/test/java/com/app/integration/summaryfile/aigeneratedsummary/SummaryFileIntegrationTest.java @@ -0,0 +1,184 @@ +package com.app.integration.summaryfile.aigeneratedsummary; + +import com.app.domain.file.dto.Response.FileListResponseDto; +import com.app.domain.summary.aigeneratedsummary.dto.SummaryFile.Request.AiGenerateSummaryDto; +import com.app.domain.summary.aigeneratedsummary.dto.SummaryFile.Response.AiGenerateSummaryResponseDto; +import com.app.global.config.ENUM.Amount; +import com.app.global.config.S3.S3Service; +import com.app.integration.dto.FakeSignUpRequest; +import com.jayway.jsonpath.TypeRef; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; + +import java.io.IOException; + +import static org.hibernate.validator.internal.util.Contracts.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.util.AssertionErrors.assertFalse; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SummaryFileIntegrationTest { + + @LocalServerPort + int port; + + @MockBean + private S3Service s3Service; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @Test + @DisplayName("정상적으로 텍스트기반 AI 요약정리를 생성한다") + void 텍스트기반_요약파일_생성_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + AiGenerateSummaryDto aiGenerateSummaryDto = new AiGenerateSummaryDto( + "요약할 텍스트 예시", + Amount.FEW, + "요약파일명"); + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(aiGenerateSummaryDto) + .when().post("/api/summaryFile/generateSummaryFileByText") + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertNotNull(response.jsonPath().getLong("fileId")); + assertNotNull(response.jsonPath().getString("summaryTitle")); + } + + @Test + @DisplayName("이미지 기반 요약 파일 생성") + void 이미지기반_요약파일_생성_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + AiGenerateSummaryDto aiGenerateSummaryDto = new AiGenerateSummaryDto( + "Sample Image Text", + Amount.FEW, + "요약파일명"); + + // 테스트 이미지 파일 읽기 + ClassPathResource imageResource = new ClassPathResource("image/testImage.png"); + MockMultipartFile mockFile = null; + try { + mockFile = new MockMultipartFile("file", imageResource.getFilename(), + MediaType.IMAGE_JPEG_VALUE, imageResource.getInputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // When + ExtractableResponse response = null; + try { + response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .multiPart("file", mockFile.getOriginalFilename(), mockFile.getBytes(), mockFile.getContentType()) + .multiPart("text", aiGenerateSummaryDto.getText()) + .multiPart("amount", aiGenerateSummaryDto.getAmount().toString()) + .multiPart("fileName", aiGenerateSummaryDto.getFileName()) + .when().post("/api/summaryFile/generateSummaryFileByImage") + .then().log().all() + .extract(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertNotNull(response.jsonPath().getLong("fileId")); + assertNotNull(response.jsonPath().getString("summaryTitle")); + } + + @Test + @DisplayName("PDF 기반 요약 파일 생성") + void PDF기반_요약파일_생성_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + AiGenerateSummaryDto aiGenerateSummaryDto = new AiGenerateSummaryDto("Sample PDF Text", Amount.FEW, "PDF 요약파일명"); + + // PDF 파일을 src/test/resources에서 읽어오기 + ClassPathResource pdfResource = new ClassPathResource("pdf/pdf1.pdf"); + MockMultipartFile mockFile = null; + try { + mockFile = new MockMultipartFile("file", pdfResource.getFilename(), + MediaType.APPLICATION_PDF_VALUE, pdfResource.getInputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // When + ExtractableResponse response = null; + try { + response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .contentType(MediaType.MULTIPART_FORM_DATA_VALUE) + .multiPart("file", mockFile.getOriginalFilename(), mockFile.getBytes(), mockFile.getContentType()) + .formParam("text", aiGenerateSummaryDto.getText()) + .formParam("amount", aiGenerateSummaryDto.getAmount().toString()) + .formParam("fileName", aiGenerateSummaryDto.getFileName()) + .when().post("/api/summaryFile/generateSummaryFileByPdf") + .then().log().all() + .extract(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + assertNotNull(response.jsonPath().getLong("fileId")); + assertNotNull(response.jsonPath().getString("summaryTitle")); + } + + @Test + @DisplayName("모든 요약 파일 리스트 조회") + void 모든_요약파일_리스트_조회_테스트() { + // Given + String jwtToken = memberSignUpRequest("닉네임", "email@test.com"); + int pageNumber = 1; + + // When + ExtractableResponse response = RestAssured.given().log().all() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken) + .when().get("/api/summaryFile/searchAiSummaryFileList/" + pageNumber) + .then().log().all() + .extract(); + + // Then + assertEquals(HttpStatus.OK.value(), response.statusCode()); + } + + private String memberSignUpRequest(String nickname, String email) { + FakeSignUpRequest signUpRequest = new FakeSignUpRequest(nickname, email); + ExtractableResponse signUpResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(signUpRequest) + .when().post("/api/fake/login") + .then().log().all() + .extract(); + return signUpResponse.asString(); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..c09cc01 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,37 @@ +spring: + profiles: + active: test + + datasource: + url: jdbc:h2:mem:test + username: sa + password: + driver-class-name: org.h2.Driver + +jpa: + hibernate: + ddl-auto: create + show-sql: true + properties: + hibernate: + format_sql: true + +token: + secret: 123456789 + access-token-expiration-time: 900000 # 15? 1000(ms) x 60(s) x 15(m) + refresh-token-expiration-time: 1209600000 # 2? 1000(ms) x 60 (s) x 60(m) x 24(h) x 14(d) + +cloud: + aws: #AWS S3 ?? + credentials: + accessKey: 123456789 + secretKey: 123456789 + region: + static: 123456789 + s3: + bucket: 123456789 + +kakao: + client: + id: 123456789 + secret: 123456789 diff --git a/src/test/resources/image/testImage.png b/src/test/resources/image/testImage.png new file mode 100644 index 0000000..eda4083 Binary files /dev/null and b/src/test/resources/image/testImage.png differ