diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..06032ab2f
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,3 @@
+* @chysis @BadaHertz52 @soosoo22 @ImxYJL @donghoony @Kimprodp @nayonsoso @skylar1220
+/frontend/ @chysis @BadaHertz52 @soosoo22 @ImxYJL
+/backend/ @donghoony @Kimprodp @nayonsoso @skylar1220
diff --git a/.github/workflows/backend-dev-cd.yml b/.github/workflows/backend-dev-cd.yml
index d0d2c39b2..6a10d7c00 100644
--- a/.github/workflows/backend-dev-cd.yml
+++ b/.github/workflows/backend-dev-cd.yml
@@ -57,7 +57,7 @@ jobs:
deploy:
name: Deploy via self-hosted runner
needs: build
- runs-on: [self-hosted, dev]
+ runs-on: [self-hosted, dev, oracle]
steps:
- name: Checkout to secret repository
@@ -81,7 +81,7 @@ jobs:
env:
PROFILE_VAR: "dev"
run: |
+ sudo docker pull ${{ secrets.DOCKERHUB_ID }}/review-me-app:develop
chmod +x ./deploy.sh
sudo -E ./deploy.sh
-
working-directory: ${{ env.APPLICATION_DIRECTORY }}/app
diff --git a/.github/workflows/backend-prod-cd.yml b/.github/workflows/backend-prod-cd.yml
index c010d96e3..2033f023c 100644
--- a/.github/workflows/backend-prod-cd.yml
+++ b/.github/workflows/backend-prod-cd.yml
@@ -7,6 +7,17 @@ env:
APPLICATION_DIRECTORY: /home/ubuntu/review-me
jobs:
+ check-branch:
+ name: Check branch name
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check branch name
+ if: ${{ github.ref_name != 'release' && !startsWith(github.ref_name, 'hotfix/') }}
+ run: |
+ echo "This workflow can only run on 'release' branch or branches starting with 'hotfix/'"
+ echo "Current branch: ${{ github.ref_name }}"
+ exit 1
+
build:
name: Build Dockerfile and push to DockerHub
runs-on: ubuntu-latest
@@ -52,10 +63,7 @@ jobs:
deploy:
name: Deploy via self-hosted runner
needs: build
- strategy:
- matrix:
- runner: [prod-a, prod-b]
- runs-on: [ self-hosted, "${{ matrix.runner }}" ]
+ runs-on: [self-hosted, prod, oracle]
steps:
- name: Checkout to secret repository
@@ -79,6 +87,7 @@ jobs:
env:
PROFILE_VAR: "prod"
run: |
+ sudo docker pull ${{ secrets.DOCKERHUB_ID }}/review-me-app:release
chmod +x ./deploy.sh
sudo -E ./deploy.sh
working-directory: ${{ env.APPLICATION_DIRECTORY }}/app
diff --git a/.github/workflows/discord-pull-request-comment.yml b/.github/workflows/discord-pull-request-comment.yml
index e1a3fc36b..d3594088f 100644
--- a/.github/workflows/discord-pull-request-comment.yml
+++ b/.github/workflows/discord-pull-request-comment.yml
@@ -31,7 +31,7 @@ jobs:
elif [ "$PR_PREFIX" = '[FE]' ]; then
echo Frontend PR Found!
echo "PR_PREFIX=FE" >> $GITHUB_ENV
- elif [ "$PR_PREFIX" = '[All]' ]; then
+ elif [ "$PR_PREFIX" = '[All]' ] || [ "$PR_PREFIX" = '[Release]' ]; then
echo All PR Found!
echo "PR_PREFIX=All" >> $GITHUB_ENV
fi
diff --git a/.github/workflows/discord-pull-request.yml b/.github/workflows/discord-pull-request.yml
index 52b446ebb..9f9919b42 100644
--- a/.github/workflows/discord-pull-request.yml
+++ b/.github/workflows/discord-pull-request.yml
@@ -31,7 +31,7 @@ jobs:
elif [ "$PR_PREFIX" = '[FE]' ]; then
echo Frontend PR Found!
echo "PR_PREFIX=FE" >> $GITHUB_ENV
- elif [ "$PR_PREFIX" = '[All]' ]; then
+ elif [ "$PR_PREFIX" = '[All]' ] || [ "$PR_PREFIX" = '[Release]' ]; then
echo All PR Found!
echo "PR_PREFIX=All" >> $GITHUB_ENV
fi
diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml
index 689733362..a64b9a52f 100644
--- a/.github/workflows/frontend-ci.yml
+++ b/.github/workflows/frontend-ci.yml
@@ -10,9 +10,9 @@ permissions:
contents: read
pages: write
id-token: write
-concurrency:
+concurrency:
group: "ci-group"
- cancel-in-progress: false # NOTE: 기존 CI가 돌고 있는 상황에서 새 작업이 추가돼도 기존 작업 계속 수행
+ cancel-in-progress: false # 기존 작업 계속 수행
jobs:
build:
runs-on: ubuntu-latest
@@ -28,11 +28,31 @@ jobs:
cache-dependency-path: ./frontend/yarn.lock
- name: Create .env file
- run: echo "API_BASE_URL=${{ secrets.API_BASE_URL }}" > ./frontend/.env
+ run: |
+ echo "API_BASE_URL=${{ secrets.API_BASE_URL }}" > ./frontend/.env
+ echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> ./frontend/.env
+ echo "AMPLITUDE_KEY=${{ secrets.AMPLITUDE_KEY }}" >> ./frontend/.env
- name: Set environment file permissions
run: chmod 644 ./frontend/.env
+ # 프리플라이트 체크
+ - name: Preflight Check for Environment Variables
+ run: |
+ if [ -z "${{ secrets.API_BASE_URL }}" ]; then
+ echo "Error: API_BASE_URL is not set"
+ exit 1
+ fi
+ if [ -z "${{ secrets.SENTRY_AUTH_TOKEN }}" ]; then
+ echo "Error: SENTRY_AUTH_TOKEN is not set"
+ exit 1
+ fi
+ if [ -z "${{ secrets.AMPLITUDE_KEY }}" ]; then
+ echo "Error: AMPLITUDE_KEY is not set"
+ exit 1
+ fi
+ shell: bash
+
- name: Install dependencies
run: yarn install --frozen-lockfile
working-directory: frontend
@@ -45,4 +65,6 @@ jobs:
run: yarn build
env:
API_BASE_URL: ${{ secrets.API_BASE_URL }}
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+ AMPLITUDE_KEY: ${{ secrets.AMPLITUDE_KEY }}
working-directory: frontend
diff --git a/README.md b/README.md
index c7e1b40ba..63ae0b310 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,65 @@
-# 리뷰미
+
-> 🤔 우리 팀원은 나를 어떻게 생각할까?
-> 🫂 나와 팀이 함께 성장하려면 어떻게 해야 할까?
-> 🤨 팀원에게 하고 싶은 말이 있는데, 대면으로 하기가 민망하네..
-> 🥹 기능 구현 하기에도 바빠서 문화를 챙길 시간도 없고, 팀원들한테 이런거 하자고 하기도 부담스러워...
+🔗[리뷰미 바로가기](https://review-me.page)
-저희도 스스로가 팀에서 어떤 존재인지 고민될 때가 있습니다.
+# 🔎 리뷰미
-동료의 피드백을 통해 저희는 자신의 강점과 팀에 어떻게 기여할 수 있는지를 알게 되었습니다.
-지칠 때 받은 동료의 리뷰가 큰 힘이 되었어요. 팀원 모두가 서로를 응원하니 자연스럽게 팀워크도 향상됐습니다.
-리뷰미는 동료로부터 기술뿐만 아니라 소프트 스킬, 나의 특징 등을 다방면으로 리뷰 받을 수 있는 서비스입니다.
-리뷰미를 통해 협업하는 내 모습을 알아갈 수 있고, 나아가 함께 성장하는 방식을 고민할 수 있습니다.
-어쩌면 내가 몰랐던 내 모습을 발견할 수도 있겠죠?
+## 프로젝트 소개
+프로젝트를 함께한 동료들에게 받은 리뷰를 통해 자신이 어떤 개발자인지 파악하고 표현하는 데 도움을 주는 서비스입니다.
+기술뿐만 아니라 소프트 스킬, 나의 강점 등을 다방면으로 리뷰 받을 수 있어요.
+어쩌면 내가 몰랐던 내 모습을 발견할 수도 있겠죠?
+
+## 리뷰미가 세상에 나온 이유✨
+> 🤔 나는 무엇을 잘하는 개발자일까?
+📚 어떤 점을 보완하면 내가 더 성장할 수 있을까?
+🫂 우리 팀원은 나를 어떻게 생각할까?
+
+프로젝트를 하다보면 이런 고민이 들 때가 있지 않나요?
+우리는 이 고민의 답을 `동료들의 피드백`에서 찾았어요.
+동료들과 피드백을 주고받으며 `내가 팀에서 어떤 사람`이었고 `무엇을 잘하는지` 알 수 있었기 때문이에요.
+
+그렇게 동료들과 피드백을 주고받을 수 있는 서비스, `리뷰미`가 탄생하였습니다.
+
+## 주요 기능 소개
+
+### 리뷰를 작성해보세요
+뭐라고 리뷰를 써야할지 막막한가요? 리뷰미를 통해 그 때의 기억을 떠올리며 리뷰를 작성해보세요.
+
+
+
+### 리뷰를 확인해보세요
+팀원들이 보는 내 모습은 어땠을까요? 작성한 리뷰를 확인해보세요!
+
+
+
+### 리뷰로 나를 파악해보세요
+받은 리뷰를 모아보고, 나를 파악하는데 도움이 된 부분을 형광펜으로 표시할 수 있어요.
+
+
+## 😮 리뷰미 서비스 사용 후기
+
+
+
+## ⚙️ 기술 스택
+### 프론트엔드
+
+
+### 백엔드
+
+
+### Infrastructure
+
+
+## 🧑💻 팀원 소개
+
+### 프론트엔드
+| | | | |
+| :---: | :---: | :---: | :---: |
+| [🐋 바다](https://github.com/badahertz52) | [😍 쑤쑤](https://github.com/soosoo22) | [🔥 에프이](https://github.com/chysis) | [👾 올리](https://github.com/ImxYJL) |
+
+### 백엔드
+| | | | |
+| :---: | :---: | :---: | :---: |
+| [🦧 산초](https://github.com/nayonsoso) | [🤸🏻♂️ 아루](https://github.com/donghoony) | [💃 커비](https://github.com/skylar1220) | [🐻 테드](https://github.com/Kimprodp) |
-여러분들도 리뷰를 통한 좋은 경험을 해보고 싶으시다면,
-리뷰를 통해 누군가에게 응원을 전달하고 싶으시다면,
-리뷰미와 함께하세요!
diff --git a/backend/build.gradle b/backend/build.gradle
index cea87d9cf..974a88f3e 100644
--- a/backend/build.gradle
+++ b/backend/build.gradle
@@ -29,27 +29,31 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
- implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
+ implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
- implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+ implementation 'com.h2database:h2'
- runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
- annotationProcessor 'org.projectlombok:lombok'
+
+ // ConfigurationProperties
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
+ // Test dependencies
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'
+
+ // Lombok
+ annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.projectlombok:lombok'
- testImplementation 'org.springframework.boot:spring-boot-starter-test'
// RestDocs
- asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:3.0.1'
- testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:3.0.1'
- testImplementation 'io.rest-assured:spring-mock-mvc:5.4.0'
- testImplementation 'io.rest-assured:rest-assured:5.4.0'
+ asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
+ testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
+ testImplementation 'io.rest-assured:spring-mock-mvc'
+ testImplementation 'io.rest-assured:rest-assured'
}
ext {
diff --git a/backend/src/docs/asciidoc/auth.adoc b/backend/src/docs/asciidoc/auth.adoc
new file mode 100644
index 000000000..17900fe72
--- /dev/null
+++ b/backend/src/docs/asciidoc/auth.adoc
@@ -0,0 +1,7 @@
+==== 깃허브로 로그인/회원가입
+
+operation::github-auth[snippets="curl-request,request-fields,http-response"]
+
+==== 로그아웃
+
+operation::logout[snippets="curl-request,request-cookies,http-response"]
diff --git a/backend/src/docs/asciidoc/create-review.adoc b/backend/src/docs/asciidoc/create-review.adoc
index 7b3464613..e05ef1b90 100644
--- a/backend/src/docs/asciidoc/create-review.adoc
+++ b/backend/src/docs/asciidoc/create-review.adoc
@@ -1,6 +1,10 @@
-==== 리뷰 생성
+==== 비회원이 리뷰 생성
-operation::create-review[snippets="curl-request,request-fields,http-response"]
+operation::create-review-by-guest[snippets="curl-request,request-fields,http-response"]
+
+==== 회원이 리뷰 생성
+
+operation::create-review-by-member[snippets="curl-request,request-fields,http-response"]
==== 그룹 코드가 올바르지 않은 경우
diff --git a/backend/src/docs/asciidoc/index.adoc b/backend/src/docs/asciidoc/index.adoc
index 4d67754a7..0575b8a24 100644
--- a/backend/src/docs/asciidoc/index.adoc
+++ b/backend/src/docs/asciidoc/index.adoc
@@ -40,3 +40,11 @@ include::review-gather.adoc[]
=== 답변 하이라이트
include::highlight-answers.adoc[]
+
+== 인증
+
+include::auth.adoc[]
+
+== 사용자
+
+include::member.adoc[]
\ No newline at end of file
diff --git a/backend/src/docs/asciidoc/member.adoc b/backend/src/docs/asciidoc/member.adoc
new file mode 100644
index 000000000..9a51e2c94
--- /dev/null
+++ b/backend/src/docs/asciidoc/member.adoc
@@ -0,0 +1,3 @@
+==== 내 프로필 정보
+
+operation::my-profile[snippets="curl-request,request-cookies,http-response,response-fields"]
\ No newline at end of file
diff --git a/backend/src/docs/asciidoc/review-list.adoc b/backend/src/docs/asciidoc/review-list.adoc
index 3d8648566..d0cf479ba 100644
--- a/backend/src/docs/asciidoc/review-list.adoc
+++ b/backend/src/docs/asciidoc/review-list.adoc
@@ -1,3 +1,7 @@
==== 자신이 받은 리뷰 목록 조회
operation::received-review-list-with-pagination[snippets="curl-request,request-cookies,query-parameters,http-response,response-fields"]
+
+==== 자신이 작성한 리뷰 목록 조회
+
+operation::authored-review-list-with-pagination[snippets="curl-request,query-parameters,http-response,response-fields"]
diff --git a/backend/src/docs/asciidoc/reviewgroup.adoc b/backend/src/docs/asciidoc/reviewgroup.adoc
index c3eb2f803..3b47c4228 100644
--- a/backend/src/docs/asciidoc/reviewgroup.adoc
+++ b/backend/src/docs/asciidoc/reviewgroup.adoc
@@ -1,11 +1,23 @@
-==== 리뷰 그룹 생성
+==== 비회원용 리뷰 그룹 생성
-operation::review-group-create[snippets="curl-request,request-fields,http-response,response-fields"]
+operation::guest-review-group-create[snippets="curl-request,request-fields,http-response,response-fields"]
-==== 리뷰 그룹 간단 정보 조회
+==== 회원용 리뷰 그룹 생성
-operation::review-group-summary[snippets="curl-request,http-response,response-fields"]
+operation::member-review-group-create[snippets="curl-request,request-fields,http-response,response-fields"]
+
+==== 회원이 만든 리뷰 그룹 간단 정보 조회
+
+operation::member-review-group-summary[snippets="curl-request,http-response,response-fields"]
+
+==== 비회원이 만든 리뷰 그룹 간단 정보 조회
+
+operation::guest-review-group-summary[snippets="curl-request,http-response,response-fields"]
==== 리뷰 요청 코드, 확인 코드 일치 여부
operation::review-group-check-access[snippets="curl-request,request-fields,http-response,response-cookies"]
+
+==== 자신이 만든 리뷰 그룹 목록 조회
+
+operation::review-group-list[snippets="curl-request,request-cookies,http-response,response-fields"]
diff --git a/backend/src/main/java/reviewme/DatabaseInitializer.java b/backend/src/main/java/reviewme/DatabaseInitializer.java
index 38d1e3496..aad6c16b8 100644
--- a/backend/src/main/java/reviewme/DatabaseInitializer.java
+++ b/backend/src/main/java/reviewme/DatabaseInitializer.java
@@ -5,18 +5,14 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.OptionType;
-import reviewme.question.domain.Question;
-import reviewme.question.domain.QuestionType;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.OptionType;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
import reviewme.template.domain.Section;
import reviewme.template.domain.Template;
import reviewme.template.domain.VisibleType;
-import reviewme.template.repository.SectionRepository;
import reviewme.template.repository.TemplateRepository;
@Component
@@ -28,12 +24,11 @@ public class DatabaseInitializer {
private static final int KEYWORD_CHECKBOX_MIN_COUNT = 1;
private static final int KEYWORD_CHECKBOX_MAX_COUNT = 2;
- private final QuestionRepository questionRepository;
- private final OptionItemRepository optionItemRepository;
- private final OptionGroupRepository optionGroupRepository;
- private final SectionRepository sectionRepository;
private final TemplateRepository templateRepository;
+ // TODO: 하드코딩되어 있는 ID를 사용하지 않도록 한다. Factory 혹은 Builder를 활용해 Template 하나를 저장하도록 한다.
+ // TODO: 어드민 페이지를 활용해 Template을 관리하는 것이 추후 유지보수에 훨씬 이득일 수 있다.
+
@PostConstruct
@Transactional
public void setup() {
@@ -43,104 +38,128 @@ public void setup() {
}
// 카테고리 선택 섹션
- long categoryQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "프로젝트 기간 동안, ${revieweeName}의 강점이 드러났던 순간을 선택해주세요.", null, 1)).getId();
- long categorySectionId = sectionRepository.save(new Section(VisibleType.ALWAYS, List.of(categoryQuestionId), null, "강점 발견", "${revieweeName/와:과} 함께 한 기억을 떠올려볼게요.", 1)).getId();
- long categoryOptionGroupId = optionGroupRepository.save(new OptionGroup(categoryQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
- long communicationOptionId = optionItemRepository.save(new OptionItem("🗣️커뮤니케이션, 협업 능력 (예: 팀원간의 원활한 정보 공유, 명확한 의사소통)", categoryOptionGroupId, 1, OptionType.CATEGORY)).getId();
- long problemSolvingOptionId = optionItemRepository.save(new OptionItem("💡문제 해결 능력 (예: 프로젝트 중 만난 버그/오류를 분석하고 이를 해결하는 능력)",categoryOptionGroupId,2, OptionType.CATEGORY )).getId();
- long timeManagingOptionId = optionItemRepository.save(new OptionItem("⏰시간 관리 능력 (예: 일정과 마감 기한 준수, 업무의 우선 순위 분배)",categoryOptionGroupId,3, OptionType.CATEGORY )).getId();
- long technicalOptionId = optionItemRepository.save(new OptionItem("💻기술적 역량, 전문 지식 (예: 요구 사항을 이해하고 이를 구현하는 능력)",categoryOptionGroupId,4, OptionType.CATEGORY )).getId();
- long growthOptionId = optionItemRepository.save(new OptionItem("🌱성장 마인드셋 (예: 새로운 분야나 잘 모르는 분야에 도전하는 마음, 꾸준한 노력으로 프로젝트 이전보다 성장하는 모습)",categoryOptionGroupId,5, OptionType.CATEGORY )).getId();
+ OptionItem communicationOptionItem = new OptionItem("🗣️커뮤니케이션, 협업 능력 (예: 팀원간의 원활한 정보 공유, 명확한 의사소통)", 1, OptionType.CATEGORY);
+ OptionItem problemSolvingOptionItem = new OptionItem("💡문제 해결 능력 (예: 프로젝트 중 만난 버그/오류를 분석하고 이를 해결하는 능력)", 2, OptionType.CATEGORY);
+ OptionItem timeManagementOptionItem = new OptionItem("⏰시간 관리 능력 (예: 일정과 마감 기한 준수, 업무의 우선 순위 분배)", 3, OptionType.CATEGORY);
+ OptionItem technicalOptionItem = new OptionItem("💻기술적 역량, 전문 지식 (예: 요구 사항을 이해하고 이를 구현하는 능력)", 4, OptionType.CATEGORY);
+ OptionItem mindsetOptionItem = new OptionItem("🌱성장 마인드셋 (예: 새로운 분야나 잘 모르는 분야에 도전하는 마음, 꾸준한 노력으로 프로젝트 이전보다 성장하는 모습)", 5, OptionType.CATEGORY);
+ OptionGroup categorySectionOptionGroup = new OptionGroup(
+ List.of(communicationOptionItem, problemSolvingOptionItem, timeManagementOptionItem, technicalOptionItem, mindsetOptionItem),
+ KEYWORD_CHECKBOX_MIN_COUNT,
+ KEYWORD_CHECKBOX_MAX_COUNT
+ );
+ Question categorySectionQuestion = new Question(true, QuestionType.CHECKBOX, categorySectionOptionGroup, "프로젝트 기간 동안, ${revieweeName}의 강점이 드러났던 순간을 선택해주세요.", null, 1);
+ Section categorySection = new Section(VisibleType.ALWAYS, List.of(categorySectionQuestion), null, "강점 발견", "${revieweeName/와:과} 함께 한 기억을 떠올려볼게요.", 1);
// 커뮤니케이션 능력 섹션
- long checkBoxCommunicationQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
- long textCommunicationQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 팀이 원활한 소통을 이뤘거나, 함께 일하면서 배울 점이 있었는지 떠올려 보세요.", 2)).getId();
- long communicationSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxCommunicationQuestionId, textCommunicationQuestionId), communicationOptionId, "커뮤니케이션 능력", CATEGORY_HEADER, 2)).getId();
- long communicationOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxCommunicationQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
- optionItemRepository.save(new OptionItem("반대 의견을 내더라도 듣는 사람이 기분 나쁘지 않게 이야기해요.",communicationOptionGroupId,1, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("팀원들의 의견을 잘 모아서 회의가 매끄럽게 진행되도록 해요.",communicationOptionGroupId,2, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("팀의 분위기를 주도해요.",communicationOptionGroupId,3, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("주장을 이야기할 때에는 합당한 근거가 뒤따라요.",communicationOptionGroupId,4, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("팀에게 필요한 것과 그렇지 않은 것을 잘 구분해요.",communicationOptionGroupId,5, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("팀 내 주어진 요구사항에 우선순위를 잘 매겨요.",communicationOptionGroupId,6, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("서로 다른 분야간의 소통도 중요하게 생각해요.",communicationOptionGroupId,7, OptionType.KEYWORD ));
+ OptionGroup communicationOptionGroup = new OptionGroup(
+ List.of(
+ new OptionItem("반대 의견을 내더라도 듣는 사람이 기분 나쁘지 않게 이야기해요.", 1, OptionType.KEYWORD),
+ new OptionItem("팀원들의 의견을 잘 모아서 회의가 매끄럽게 진행되도록 해요.", 2, OptionType.KEYWORD),
+ new OptionItem("팀의 분위기를 주도해요.", 3, OptionType.KEYWORD),
+ new OptionItem("주장을 이야기할 때에는 합당한 근거가 뒤따라요.", 4, OptionType.KEYWORD),
+ new OptionItem("팀에게 필요한 것과 그렇지 않은 것을 잘 구분해요.", 5, OptionType.KEYWORD),
+ new OptionItem("팀 내 주어진 요구사항에 우선순위를 잘 매겨요.", 6, OptionType.KEYWORD),
+ new OptionItem("서로 다른 분야간의 소통도 중요하게 생각해요.", 7, OptionType.KEYWORD)
+ ),
+ KEYWORD_CHECKBOX_MIN_COUNT,
+ KEYWORD_CHECKBOX_MAX_COUNT
+ );
+ Question communicationSectionQuestion = new Question(true, QuestionType.CHECKBOX, communicationOptionGroup, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1);
+ Question communicationSectionTextQuestion = new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 팀이 원활한 소통을 이뤘거나, 함께 일하면서 배울 점이 있었는지 떠올려 보세요.", 2);
+ Section communicationSection = new Section(VisibleType.CONDITIONAL, List.of(communicationSectionQuestion, communicationSectionTextQuestion), communicationOptionItem, "커뮤니케이션 능력", CATEGORY_HEADER, 2);
// 문제해결 능력 섹션
- long checkBoxProblemSolvingQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "문제해결 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
- long textProblemSolvingQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. 어떤 문제 상황이 발생했고, ${revieweeName/가:이} 어떻게 해결했는지 그 과정을 떠올려 보세요.", 2)).getId();
- long problemSolvingSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxProblemSolvingQuestionId, textProblemSolvingQuestionId), problemSolvingOptionId, "문제해결 능력", CATEGORY_HEADER, 3)).getId();
- long problemSolvingOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxProblemSolvingQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
- optionItemRepository.save(new OptionItem("큰 문제를 작은 단위로 쪼개서 단계별로 해결해나가요.",problemSolvingOptionGroupId,1, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("낯선 문제를 만나도 당황하지 않고 차분하게 풀어나가요.",problemSolvingOptionGroupId,2, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("문제 해결을 위해 GPT등의 자원을 적극적으로 활용해요.",problemSolvingOptionGroupId,3, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("문제를 해결한 뒤에도 재발 방지를 위한 노력을 기울여요. (예: 문서화, 테스트 케이스 추가 등)",problemSolvingOptionGroupId,4, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("문제의 원인을 적극적으로 탐구하고 해결해요. (예: 디버깅 툴의 적극적 활용 등)",problemSolvingOptionGroupId,5, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("어려운 문제를 만나도 피하지 않고 도전해요.",problemSolvingOptionGroupId,6, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("문제를 해결하기 위해 타인과 의사소통을 할 수 있어요. (예: 팀원과 이슈 공유, 문제 상황 설명 등)",problemSolvingOptionGroupId,7, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("문제 원인과 해결책에 대한 가설을 세우고 직접 실험해봐요.",problemSolvingOptionGroupId,8, OptionType.KEYWORD ));
+ OptionGroup problemSolvingOptionGroup = new OptionGroup(
+ List.of(
+ new OptionItem("큰 문제를 작은 단위로 쪼개서 단계별로 해결해나가요.", 1, OptionType.KEYWORD),
+ new OptionItem("낯선 문제를 만나도 당황하지 않고 차분하게 풀어나가요.", 2, OptionType.KEYWORD),
+ new OptionItem("문제 해결을 위해 GPT등의 자원을 적극적으로 활용해요.", 3, OptionType.KEYWORD),
+ new OptionItem("문제를 해결한 뒤에도 재발 방지를 위한 노력을 기울여요. (예: 문서화, 테스트 케이스 추가 등)", 4, OptionType.KEYWORD),
+ new OptionItem("문제의 원인을 적극적으로 탐구하고 해결해요. (예: 디버깅 툴의 적극적 활용 등)", 5, OptionType.KEYWORD),
+ new OptionItem("어려운 문제를 만나도 피하지 않고 도전해요.", 6, OptionType.KEYWORD),
+ new OptionItem("문제를 해결하기 위해 타인과 의사소통을 할 수 있어요. (예: 팀원과 이슈 공유, 문제 상황 설명 등)", 7, OptionType.KEYWORD),
+ new OptionItem("문제 원인과 해결책에 대한 가설을 세우고 직접 실험해봐요.", 8, OptionType.KEYWORD)
+ ),
+ KEYWORD_CHECKBOX_MIN_COUNT,
+ KEYWORD_CHECKBOX_MAX_COUNT
+ );
+ Question problemSolvingSectionQuestion = new Question(true, QuestionType.CHECKBOX, problemSolvingOptionGroup, "문제해결 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 팀이 어떤 문제 상황을 만났을 때, 어떻게 해결했는지 떠올려 보세요.", 1);
+ Question problemSolvingSectionTextQuestion = new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. 어떤 문제 상황이 발생했고, ${revieweeName/가:이} 어떻게 해결했는지 그 과정을 떠올려 보세요.", 2);
+ Section problemSolvingSection = new Section(VisibleType.CONDITIONAL, List.of(problemSolvingSectionQuestion, problemSolvingSectionTextQuestion), problemSolvingOptionItem, "문제해결 능력", CATEGORY_HEADER, 3);
// 시간 관리 능력 섹션
- long checkBoxTimeManagingQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "시간 관리 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
- long textTimeManagingQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 팀이 효율적으로 시간관리를 할 수 있었는지 떠올려 보세요.", 2)).getId();
- long timeManagingSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxTimeManagingQuestionId, textTimeManagingQuestionId), timeManagingOptionId, "시간관리 능력", CATEGORY_HEADER, 4)).getId();
- long timeManagingOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxTimeManagingQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
- optionItemRepository.save(new OptionItem("프로젝트의 일정과 주요 마일스톤을 설정하여 체계적으로 일정을 관리해요.",timeManagingOptionGroupId,1, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("일정에 따라 마감 기한을 잘 지켜요.",timeManagingOptionGroupId,2, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("업무의 중요도와 긴급성을 고려하여 우선 순위를 정하고, 그에 따라 작업을 분배해요.",timeManagingOptionGroupId,3, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("예기치 않은 일정 변경에도 유연하게 대처해요.",timeManagingOptionGroupId,4, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("회의 시간과 같은 약속된 시간을 잘 지켜요.",timeManagingOptionGroupId,5, OptionType.KEYWORD ));
+ OptionGroup timeManagingOptionGroup = new OptionGroup(
+ List.of(
+ new OptionItem("프로젝트의 일정과 주요 마일스톤을 설정하여 체계적으로 일정을 관리해요.", 1, OptionType.KEYWORD),
+ new OptionItem("일정에 따라 마감 기한을 잘 지켜요.", 2, OptionType.KEYWORD),
+ new OptionItem("업무의 중요도와 긴급성을 고려하여 우선 순위를 정하고, 그에 따라 작업을 분배해요.", 3, OptionType.KEYWORD),
+ new OptionItem("예기치 않은 일정 변경에도 유연하게 대처해요.", 4, OptionType.KEYWORD),
+ new OptionItem("회의 시간과 같은 약속된 시간을 잘 지켜요.", 5, OptionType.KEYWORD)
+ ),
+ KEYWORD_CHECKBOX_MIN_COUNT,
+ KEYWORD_CHECKBOX_MAX_COUNT
+ );
+ Question timeManagingSectionQuestion = new Question(true, QuestionType.CHECKBOX, timeManagingOptionGroup, "시간 관리 능력에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1);
+ Question timeManagingSectionTextQuestion = new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 팀이 효율적으로 시간관리를 할 수 있었는지 떠올려 보세요.", 2);
+ Section timeManagingSection = new Section(VisibleType.CONDITIONAL, List.of(timeManagingSectionQuestion, timeManagingSectionTextQuestion), timeManagementOptionItem, "시간 관리 능력", CATEGORY_HEADER, 4);
// 기술 역량 섹션
- long checkBoxTechnicalQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "기술 역량, 전문 지식에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
- long textTechnicalQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 기술적 역량, 전문 지식적으로 도움을 받은 경험을 떠올려 보세요.", 2)).getId();
- long technicalSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxTechnicalQuestionId, textTechnicalQuestionId), technicalOptionId, "기술 역량", CATEGORY_HEADER, 5)).getId();
- long technicalOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxTechnicalQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
- optionItemRepository.save(new OptionItem("관련 언어 / 라이브러리 / 프레임워크 지식이 풍부해요.",technicalOptionGroupId,1, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("인프라 지식이 풍부해요.",technicalOptionGroupId,2, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("CS 지식이 풍부해요.",technicalOptionGroupId,3, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("코드 리뷰에서 중요한 개선점을 제안했어요.",technicalOptionGroupId,4, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("리팩토링을 통해 전체 코드의 품질을 향상시켰어요.",technicalOptionGroupId,5, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("복잡한 버그를 신속하게 찾고 해결했어요.",technicalOptionGroupId,6, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("꼼꼼하게 테스트를 작성했어요.",technicalOptionGroupId,7, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("처음 보는 기술을 빠르게 습득하여 팀 프로젝트에 적용했어요.",technicalOptionGroupId,8, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("명확하고 자세한 기술 문서를 작성하여 팀의 이해를 도왔어요.",technicalOptionGroupId,9, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("컨벤션을 잘 지키면서 클린 코드를 작성하려고 노력했어요.",technicalOptionGroupId,10, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("성능 최적화에 기여했어요.",technicalOptionGroupId,11, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("지속적인 학습과 공유를 통해 팀의 기술 수준을 높였어요.",technicalOptionGroupId,12, OptionType.KEYWORD ));
+ OptionGroup technicalOptionGroup = new OptionGroup(
+ List.of(
+ new OptionItem("관련 언어 / 라이브러리 / 프레임워크 지식이 풍부해요.", 1, OptionType.KEYWORD),
+ new OptionItem("인프라 지식이 풍부해요.", 2, OptionType.KEYWORD),
+ new OptionItem("CS 지식이 풍부해요.", 3, OptionType.KEYWORD),
+ new OptionItem("코드 리뷰에서 중요한 개선점을 제안했어요.", 4, OptionType.KEYWORD),
+ new OptionItem("리팩토링을 통해 전체 코드의 품질을 향상시켰어요.", 5, OptionType.KEYWORD),
+ new OptionItem("복잡한 버그를 신속하게 찾고 해결했어요.", 6, OptionType.KEYWORD),
+ new OptionItem("꼼꼼하게 테스트를 작성했어요.", 7, OptionType.KEYWORD),
+ new OptionItem("처음 보는 기술을 빠르게 습득하여 팀 프로젝트에 적용했어요.", 8, OptionType.KEYWORD),
+ new OptionItem("명확하고 자세한 기술 문서를 작성하여 팀의 이해를 도왔어요.", 9, OptionType.KEYWORD),
+ new OptionItem("컨벤션을 잘 지키면서 클린 코드를 작성하려고 노력했어요.", 10, OptionType.KEYWORD),
+ new OptionItem("성능 최적화에 기여했어요.", 11, OptionType.KEYWORD),
+ new OptionItem("지속적인 학습과 공유를 통해 팀의 기술 수준을 높였어요.", 12, OptionType.KEYWORD)
+ ),
+ KEYWORD_CHECKBOX_MIN_COUNT,
+ KEYWORD_CHECKBOX_MAX_COUNT
+ );
+ Question technicalSectionQuestion = new Question(true, QuestionType.CHECKBOX, technicalOptionGroup, "기술 역량, 전문 지식에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1);
+ Question technicalSectionTextQuestion = new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. ${revieweeName} 덕분에 기술적 역량, 전문 지식적으로 도움을 받은 경험을 떠올려 보세요.", 2);
+ Section technicalSection = new Section(VisibleType.CONDITIONAL, List.of(technicalSectionQuestion, technicalSectionTextQuestion), technicalOptionItem, "기술 역량, 전문 지식", CATEGORY_HEADER, 5);
// 성장 마인드셋 섹션
- long checkBoxGrowthQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "성장 마인드셋에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
- long textGrowthQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. 인상깊었던 ${revieweeName}의 성장 마인드셋을 떠올려 보세요.", 2)).getId();
- long growthSectionId = sectionRepository.save(new Section(VisibleType.CONDITIONAL, List.of(checkBoxGrowthQuestionId, textGrowthQuestionId), growthOptionId, "성장 마인드셋", CATEGORY_HEADER, 6)).getId();
- long growthOptionGroupId = optionGroupRepository.save(new OptionGroup(checkBoxGrowthQuestionId, KEYWORD_CHECKBOX_MIN_COUNT, KEYWORD_CHECKBOX_MAX_COUNT)).getId();
- optionItemRepository.save(new OptionItem("어떤 상황에도 긍정적인 태도로 임해요.",growthOptionGroupId,1, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("주변 사람들한테 질문하는 것을 부끄러워하지 않아요.",growthOptionGroupId,2, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("어려움이 있어도 끝까지 해내요.",growthOptionGroupId,3, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("함께 성장하기 위해, 배운 내용을 다른 사람과 공유해요.",growthOptionGroupId,4, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("새로운 것을 두려워하지 않고 적극적으로 배워나가요.",growthOptionGroupId,5, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("이론적 학습에서 그치지 않고 직접 적용하려 노력해요.",growthOptionGroupId,6, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("다른 사람들과 비교하지 않고 본인만의 속도로 성장하는 법을 알고 있어요.",growthOptionGroupId,7, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("받은 피드백을 빠르게 수용해요.",growthOptionGroupId,8, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("회고를 통해 성장할 수 있는 방법을 스스로 탐색해요.",growthOptionGroupId,9, OptionType.KEYWORD ));
- optionItemRepository.save(new OptionItem("새로운 아이디어를 시도하고, 기존의 틀을 깨는 것을 두려워하지 않아요.",growthOptionGroupId,10, OptionType.KEYWORD ));
+ OptionGroup mindsetOptionGroup = new OptionGroup(
+ List.of(
+ new OptionItem("어려운 상황에도 긍정적인 태도로 임했어요.", 1, OptionType.KEYWORD),
+ new OptionItem("주변 사람들한테 질문하는 것을 부끄러워하지 않았어요.", 2, OptionType.KEYWORD),
+ new OptionItem("어려움이 있어도 끝까지 해냈어요.", 3, OptionType.KEYWORD),
+ new OptionItem("함께 성장하기 위해, 배운 내용을 다른 사람과 공유했어요.", 4, OptionType.KEYWORD),
+ new OptionItem("새로운 것을 두려워하지 않고 적극적으로 배웠어요.", 5, OptionType.KEYWORD),
+ new OptionItem("이론적 학습에서 그치지 않고 직접 적용하려 노력했어요.", 6, OptionType.KEYWORD),
+ new OptionItem("다른 사람들과 비교하지 않고 본인만의 속도로 성장하는 법을 알고 있었어요.", 7, OptionType.KEYWORD),
+ new OptionItem("받은 피드백을 빠르게 수용했어요.", 8, OptionType.KEYWORD),
+ new OptionItem("회고를 통해 성장할 수 있는 방법을 스스로 탐색했어요.", 9, OptionType.KEYWORD),
+ new OptionItem("새로운 아이디어를 시도하고, 기존의 틀을 깨는 것을 두려워하지 않았어요.", 10, OptionType.KEYWORD)
+ ),
+ KEYWORD_CHECKBOX_MIN_COUNT,
+ KEYWORD_CHECKBOX_MAX_COUNT
+ );
+ Question mindsetSectionQuestion = new Question(true, QuestionType.CHECKBOX, mindsetOptionGroup, "성장 마인드셋에서 어느 부분이 인상 깊었는지 선택해주세요.", null, 1);
+ Question mindsetSectionTextQuestion = new Question(true, QuestionType.TEXT, CATEGORY_TEXT_QUESTION, "상황을 자세하게 기록할수록 ${revieweeName}에게 도움이 돼요. 인상깊었던 ${revieweeName}의 성장 마인드셋을 떠올려 보세요.", 2);
+ Section mindsetSection = new Section(VisibleType.CONDITIONAL, List.of(mindsetSectionQuestion, mindsetSectionTextQuestion), mindsetOptionItem, "성장 마인드셋", CATEGORY_HEADER, 6);
// 성장 목표 설정 섹션
- long textGrowthGoalQuestionId = questionRepository.save(new Question(true, QuestionType.TEXT, "앞으로의 성장을 위해서 ${revieweeName/가:이} 어떤 목표를 설정하면 좋을까요?", "어떤 점을 보완하면 좋을지와 함께 '이렇게 해보면 어떨까?'하는 간단한 솔루션을 제안해봐요.", 1)).getId();
- long textGrowthGoalSectionId = sectionRepository.save(new Section(VisibleType.ALWAYS, List.of(textGrowthGoalQuestionId), null, "보완할 점", "${revieweeName}의 성장을 도와주세요!", 7)).getId();
+ Question growthTargetSectionQuestion = new Question(true, QuestionType.TEXT, "앞으로의 성장을 위해서 ${revieweeName/가:이} 어떤 목표를 설정하면 좋을까요?", "어떤 점을 보완하면 좋을지와 함께 '이렇게 해보면 어떨까?'하는 간단한 솔루션을 제안해봐요.", 1);
+ Section growthTargetSection = new Section(VisibleType.ALWAYS, List.of(growthTargetSectionQuestion), null, "보완할 점", "${revieweeName}의 성장을 도와주세요!", 7);
// 응원의 말 섹션
- long textCheerUpQuestionId = questionRepository.save(new Question(false, QuestionType.TEXT, "${revieweeName}에게 전하고 싶은 다른 리뷰가 있거나 응원의 말이 있다면 적어주세요.", null, 1)).getId();
- long cheerUpSectionId = sectionRepository.save(new Section(VisibleType.ALWAYS, List.of(textCheerUpQuestionId), null, "추가 리뷰/응원", "리뷰를 더 하고 싶은 리뷰어를 위한 추가 리뷰!", 8)).getId();
+ Question cheerUpSectionQuestion = new Question(false, QuestionType.TEXT, "${revieweeName}에게 전하고 싶은 다른 리뷰가 있거나 응원의 말이 있다면 적어주세요.", null, 1);
+ Section cheerUpSection = new Section(VisibleType.ALWAYS, List.of(cheerUpSectionQuestion), null, "추가 리뷰/응원", "리뷰를 더 하고 싶은 리뷰어를 위한 추가 리뷰!", 8);
- templateRepository.save(new Template(List.of(
- categorySectionId,
- communicationSectionId,
- problemSolvingSectionId,
- timeManagingSectionId,
- technicalSectionId,
- growthSectionId,
- textGrowthGoalSectionId,
- cheerUpSectionId
- )));
+ Template template = new Template(
+ List.of(categorySection, communicationSection, problemSolvingSection, timeManagingSection,
+ technicalSection, mindsetSection, growthTargetSection, cheerUpSection)
+ );
+ templateRepository.save(template);
}
}
diff --git a/backend/src/main/java/reviewme/auth/controller/AuthController.java b/backend/src/main/java/reviewme/auth/controller/AuthController.java
new file mode 100644
index 000000000..14573f2fe
--- /dev/null
+++ b/backend/src/main/java/reviewme/auth/controller/AuthController.java
@@ -0,0 +1,33 @@
+package reviewme.auth.controller;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+import reviewme.auth.service.AuthService;
+import reviewme.auth.service.dto.GithubCodeRequest;
+
+@RestController
+@RequiredArgsConstructor
+public class AuthController {
+
+ private final AuthService authService;
+
+ @PostMapping("/v2/auth/github")
+ public ResponseEntity authWithGithub(
+ @Valid @RequestBody GithubCodeRequest request,
+ HttpServletRequest httpRequest
+ ) {
+ return ResponseEntity.ok().build();
+ }
+
+ @PostMapping("/v2/auth/logout")
+ public ResponseEntity logout(
+ HttpServletRequest httpRequest
+ ) {
+ return ResponseEntity.noContent().build();
+ }
+}
diff --git a/backend/src/main/java/reviewme/auth/service/AuthService.java b/backend/src/main/java/reviewme/auth/service/AuthService.java
new file mode 100644
index 000000000..5458807af
--- /dev/null
+++ b/backend/src/main/java/reviewme/auth/service/AuthService.java
@@ -0,0 +1,7 @@
+package reviewme.auth.service;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class AuthService {
+}
diff --git a/backend/src/main/java/reviewme/auth/service/dto/GithubCodeRequest.java b/backend/src/main/java/reviewme/auth/service/dto/GithubCodeRequest.java
new file mode 100644
index 000000000..b26511917
--- /dev/null
+++ b/backend/src/main/java/reviewme/auth/service/dto/GithubCodeRequest.java
@@ -0,0 +1,8 @@
+package reviewme.auth.service.dto;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record GithubCodeRequest(
+ @NotBlank(message = "깃허브 임시 코드를 입력해주세요.")
+ String code) {
+}
diff --git a/backend/src/main/java/reviewme/config/CacheManagerConfig.java b/backend/src/main/java/reviewme/config/CacheManagerConfig.java
new file mode 100644
index 000000000..21151523f
--- /dev/null
+++ b/backend/src/main/java/reviewme/config/CacheManagerConfig.java
@@ -0,0 +1,19 @@
+package reviewme.config;
+
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+@Configuration
+@EnableCaching
+public class CacheManagerConfig {
+
+ @Profile({"local", "dev", "prod"})
+ @Bean
+ public CacheManager cacheManager() {
+ return new ConcurrentMapCacheManager();
+ }
+}
diff --git a/backend/src/main/java/reviewme/config/RequestLimitProperties.java b/backend/src/main/java/reviewme/config/RequestLimitProperties.java
deleted file mode 100644
index efea3b4f8..000000000
--- a/backend/src/main/java/reviewme/config/RequestLimitProperties.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package reviewme.config;
-
-import java.time.Duration;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties(prefix = "request-limit")
-public record RequestLimitProperties(
- long threshold,
- Duration duration,
- String host,
- int port
-) {
-}
diff --git a/backend/src/main/java/reviewme/config/RequestLimitRedisConfig.java b/backend/src/main/java/reviewme/config/RequestLimitRedisConfig.java
deleted file mode 100644
index a8307db5f..000000000
--- a/backend/src/main/java/reviewme/config/RequestLimitRedisConfig.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package reviewme.config;
-
-import lombok.RequiredArgsConstructor;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.serializer.GenericToStringSerializer;
-
-@Configuration
-@EnableConfigurationProperties(RequestLimitProperties.class)
-@RequiredArgsConstructor
-public class RequestLimitRedisConfig {
-
- private final RequestLimitProperties requestLimitProperties;
-
- @Bean
- public RedisConnectionFactory redisConnectionFactory() {
- return new LettuceConnectionFactory(
- requestLimitProperties.host(), requestLimitProperties.port()
- );
- }
-
- @Bean
- public RedisTemplate requestLimitRedisTemplate() {
- RedisTemplate redisTemplate = new RedisTemplate<>();
- redisTemplate.setConnectionFactory(redisConnectionFactory());
- redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Long.class));
-
- return redisTemplate;
- }
-}
diff --git a/backend/src/main/java/reviewme/config/WebConfig.java b/backend/src/main/java/reviewme/config/WebConfig.java
index 916ea5a41..d855040f0 100644
--- a/backend/src/main/java/reviewme/config/WebConfig.java
+++ b/backend/src/main/java/reviewme/config/WebConfig.java
@@ -3,11 +3,8 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
-import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import reviewme.global.RequestLimitInterceptor;
import reviewme.reviewgroup.controller.ReviewGroupSessionResolver;
import reviewme.reviewgroup.service.ReviewGroupService;
@@ -16,16 +13,9 @@
public class WebConfig implements WebMvcConfigurer {
private final ReviewGroupService reviewGroupService;
- private final RedisTemplate redisTemplate;
- private final RequestLimitProperties requestLimitProperties;
@Override
public void addArgumentResolvers(List resolvers) {
resolvers.add(new ReviewGroupSessionResolver(reviewGroupService));
}
-
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new RequestLimitInterceptor(redisTemplate, requestLimitProperties));
- }
}
diff --git a/backend/src/main/java/reviewme/config/CorsConfig.java b/backend/src/main/java/reviewme/config/cors/CorsConfig.java
similarity index 90%
rename from backend/src/main/java/reviewme/config/CorsConfig.java
rename to backend/src/main/java/reviewme/config/cors/CorsConfig.java
index 2f51720ea..ff1a483a3 100644
--- a/backend/src/main/java/reviewme/config/CorsConfig.java
+++ b/backend/src/main/java/reviewme/config/cors/CorsConfig.java
@@ -1,18 +1,16 @@
-package reviewme.config;
+package reviewme.config.cors;
import lombok.RequiredArgsConstructor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+@Slf4j
public class CorsConfig {
- private static final Logger log = LoggerFactory.getLogger(CorsConfig.class);
-
private CorsConfig() {
}
diff --git a/backend/src/main/java/reviewme/config/CorsProperties.java b/backend/src/main/java/reviewme/config/cors/CorsProperties.java
similarity index 91%
rename from backend/src/main/java/reviewme/config/CorsProperties.java
rename to backend/src/main/java/reviewme/config/cors/CorsProperties.java
index 69a7d1c4d..e11a93667 100644
--- a/backend/src/main/java/reviewme/config/CorsProperties.java
+++ b/backend/src/main/java/reviewme/config/cors/CorsProperties.java
@@ -1,4 +1,4 @@
-package reviewme.config;
+package reviewme.config.cors;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
diff --git a/backend/src/main/java/reviewme/config/DataSourceType.java b/backend/src/main/java/reviewme/config/datasource/DataSourceType.java
similarity index 62%
rename from backend/src/main/java/reviewme/config/DataSourceType.java
rename to backend/src/main/java/reviewme/config/datasource/DataSourceType.java
index c48080ab4..b40750df2 100644
--- a/backend/src/main/java/reviewme/config/DataSourceType.java
+++ b/backend/src/main/java/reviewme/config/datasource/DataSourceType.java
@@ -1,4 +1,4 @@
-package reviewme.config;
+package reviewme.config.datasource;
public enum DataSourceType {
READ,
diff --git a/backend/src/main/java/reviewme/config/ReplicationDatasourceConfig.java b/backend/src/main/java/reviewme/config/datasource/ReplicationDatasourceConfig.java
similarity index 98%
rename from backend/src/main/java/reviewme/config/ReplicationDatasourceConfig.java
rename to backend/src/main/java/reviewme/config/datasource/ReplicationDatasourceConfig.java
index 6a33a9e08..fb59b2498 100644
--- a/backend/src/main/java/reviewme/config/ReplicationDatasourceConfig.java
+++ b/backend/src/main/java/reviewme/config/datasource/ReplicationDatasourceConfig.java
@@ -1,4 +1,4 @@
-package reviewme.config;
+package reviewme.config.datasource;
import java.util.HashMap;
import java.util.Map;
@@ -54,4 +54,3 @@ public DataSource dataSource(@Qualifier(ROUTING_DATA_SOURCE_NAME) DataSource rou
return new LazyConnectionDataSourceProxy(routingDataSource);
}
}
-
diff --git a/backend/src/main/java/reviewme/config/ReplicationRoutingDataSource.java b/backend/src/main/java/reviewme/config/datasource/ReplicationRoutingDataSource.java
similarity index 93%
rename from backend/src/main/java/reviewme/config/ReplicationRoutingDataSource.java
rename to backend/src/main/java/reviewme/config/datasource/ReplicationRoutingDataSource.java
index 49b7aa22b..f8a802467 100644
--- a/backend/src/main/java/reviewme/config/ReplicationRoutingDataSource.java
+++ b/backend/src/main/java/reviewme/config/datasource/ReplicationRoutingDataSource.java
@@ -1,4 +1,4 @@
-package reviewme.config;
+package reviewme.config.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;
diff --git a/backend/src/main/java/reviewme/global/GlobalExceptionHandler.java b/backend/src/main/java/reviewme/global/GlobalExceptionHandler.java
index 9d4511618..7724dd90e 100644
--- a/backend/src/main/java/reviewme/global/GlobalExceptionHandler.java
+++ b/backend/src/main/java/reviewme/global/GlobalExceptionHandler.java
@@ -22,7 +22,6 @@
import org.springframework.web.servlet.resource.NoResourceFoundException;
import reviewme.global.exception.BadRequestException;
import reviewme.global.exception.DataInconsistencyException;
-import reviewme.global.exception.TooManyRequestException;
import reviewme.global.exception.FieldErrorResponse;
import reviewme.global.exception.NotFoundException;
import reviewme.global.exception.UnauthorizedException;
@@ -51,11 +50,6 @@ public ProblemDetail handleDataConsistencyException(DataInconsistencyException e
return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getErrorMessage());
}
- @ExceptionHandler(TooManyRequestException.class)
- public ProblemDetail handleDuplicateRequestException(TooManyRequestException ex) {
- return ProblemDetail.forStatusAndDetail(HttpStatus.TOO_MANY_REQUESTS, ex.getErrorMessage());
- }
-
@ExceptionHandler(Exception.class)
public ProblemDetail handleException(Exception ex) {
log.error("Internal server error has occurred", ex);
diff --git a/backend/src/main/java/reviewme/global/RequestLimitInterceptor.java b/backend/src/main/java/reviewme/global/RequestLimitInterceptor.java
deleted file mode 100644
index b5747dfd1..000000000
--- a/backend/src/main/java/reviewme/global/RequestLimitInterceptor.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package reviewme.global;
-
-import static org.springframework.http.HttpHeaders.USER_AGENT;
-
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
-import org.springframework.http.HttpMethod;
-import org.springframework.stereotype.Component;
-import org.springframework.web.servlet.HandlerInterceptor;
-import reviewme.config.RequestLimitProperties;
-import reviewme.global.exception.TooManyRequestException;
-
-@Component
-@EnableConfigurationProperties(RequestLimitProperties.class)
-@RequiredArgsConstructor
-public class RequestLimitInterceptor implements HandlerInterceptor {
-
- private final RedisTemplate redisTemplate;
- private final RequestLimitProperties requestLimitProperties;
-
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
- if (!HttpMethod.POST.matches(request.getMethod())) {
- return true;
- }
-
- String key = generateRequestKey(request);
- ValueOperations valueOperations = redisTemplate.opsForValue();
- valueOperations.setIfAbsent(key, 0L, requestLimitProperties.duration());
- redisTemplate.expire(key, requestLimitProperties.duration());
-
- long requestCount = valueOperations.increment(key);
- if (requestCount > requestLimitProperties.threshold()) {
- throw new TooManyRequestException(key);
- }
- return true;
- }
-
- private String generateRequestKey(HttpServletRequest request) {
- String requestURI = request.getRequestURI();
- String remoteAddr = request.getRemoteAddr();
- String userAgent = request.getHeader(USER_AGENT);
-
- return String.format("RequestURI: %s, RemoteAddr: %s, UserAgent: %s", requestURI, remoteAddr, userAgent);
- }
-}
diff --git a/backend/src/main/java/reviewme/global/exception/FieldErrorResponse.java b/backend/src/main/java/reviewme/global/exception/FieldErrorResponse.java
index e44edf619..ae0c678a4 100644
--- a/backend/src/main/java/reviewme/global/exception/FieldErrorResponse.java
+++ b/backend/src/main/java/reviewme/global/exception/FieldErrorResponse.java
@@ -1,8 +1,4 @@
package reviewme.global.exception;
-public record FieldErrorResponse(
- String field,
- Object value,
- String message
-) {
+public record FieldErrorResponse(String field, Object value, String message) {
}
diff --git a/backend/src/main/java/reviewme/global/exception/TooManyRequestException.java b/backend/src/main/java/reviewme/global/exception/TooManyRequestException.java
deleted file mode 100644
index 4f26fee3e..000000000
--- a/backend/src/main/java/reviewme/global/exception/TooManyRequestException.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package reviewme.global.exception;
-
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class TooManyRequestException extends ReviewMeException {
-
- public TooManyRequestException(String requestKey) {
- super("짧은 시간 안에 너무 많은 동일한 요청이 일어났어요. 잠시 후 다시 시도해주세요.");
- log.warn("Too many request received - request: {}", requestKey);
- }
-}
diff --git a/backend/src/main/java/reviewme/highlight/domain/HighlightedLines.java b/backend/src/main/java/reviewme/highlight/domain/HighlightedLines.java
index f7000ecb2..f24827d30 100644
--- a/backend/src/main/java/reviewme/highlight/domain/HighlightedLines.java
+++ b/backend/src/main/java/reviewme/highlight/domain/HighlightedLines.java
@@ -2,6 +2,8 @@
import java.util.Arrays;
import java.util.List;
+import java.util.function.Function;
+import java.util.stream.IntStream;
import lombok.Getter;
import reviewme.highlight.domain.exception.InvalidHighlightLineIndexException;
import reviewme.highlight.domain.exception.NegativeHighlightLineIndexException;
@@ -37,4 +39,12 @@ private void validateLineIndexRange(int lineIndex) {
throw new InvalidHighlightLineIndexException(lineIndex, lines.size());
}
}
+
+ public List toHighlights(long answerId) {
+ return IntStream.range(0, lines.size())
+ .mapToObj(lineIndex -> lines.get(lineIndex).getRanges().stream()
+ .map(range -> new Highlight(answerId, lineIndex, range)))
+ .flatMap(Function.identity())
+ .toList();
+ }
}
diff --git a/backend/src/main/java/reviewme/highlight/domain/exception/HighlightStartIndexExceedEndIndexException.java b/backend/src/main/java/reviewme/highlight/domain/exception/HighlightStartIndexExceedEndIndexException.java
deleted file mode 100644
index 38c99ac9a..000000000
--- a/backend/src/main/java/reviewme/highlight/domain/exception/HighlightStartIndexExceedEndIndexException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package reviewme.highlight.domain.exception;
-
-import lombok.extern.slf4j.Slf4j;
-import reviewme.global.exception.BadRequestException;
-
-@Slf4j
-public class HighlightStartIndexExceedEndIndexException extends BadRequestException {
-
- public HighlightStartIndexExceedEndIndexException(int startIndex, int endIndex) {
- super("하이라이트 끝 위치는 시작 위치보다 같거나 커야 해요.");
- log.info("Highlight start index exceed end index - startIndex: {}, endIndex: {}", startIndex, endIndex);
- }
-}
diff --git a/backend/src/main/java/reviewme/highlight/repository/HighlightJdbcRepository.java b/backend/src/main/java/reviewme/highlight/repository/HighlightJdbcRepository.java
new file mode 100644
index 000000000..b1e20ef36
--- /dev/null
+++ b/backend/src/main/java/reviewme/highlight/repository/HighlightJdbcRepository.java
@@ -0,0 +1,9 @@
+package reviewme.highlight.repository;
+
+import java.util.Collection;
+import reviewme.highlight.domain.Highlight;
+
+public interface HighlightJdbcRepository {
+
+ void saveAll(Collection highlights);
+}
diff --git a/backend/src/main/java/reviewme/highlight/repository/HighlightJdbcRepositoryImpl.java b/backend/src/main/java/reviewme/highlight/repository/HighlightJdbcRepositoryImpl.java
new file mode 100644
index 000000000..077f6a531
--- /dev/null
+++ b/backend/src/main/java/reviewme/highlight/repository/HighlightJdbcRepositoryImpl.java
@@ -0,0 +1,24 @@
+package reviewme.highlight.repository;
+
+import java.util.Collection;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
+import reviewme.highlight.domain.Highlight;
+
+@RequiredArgsConstructor
+public class HighlightJdbcRepositoryImpl implements HighlightJdbcRepository {
+
+ private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+
+ @Override
+ public void saveAll(Collection highlights) {
+ SqlParameterSource[] parameterSources = SqlParameterSourceUtils.createBatch(highlights.toArray());
+ String insertSql = """
+ INSERT INTO highlight (answer_id, line_index, start_index, end_index)
+ VALUES (:answerId, :lineIndex, :highlightRange.startIndex, :highlightRange.endIndex)
+ """;
+ namedParameterJdbcTemplate.batchUpdate(insertSql, parameterSources);
+ }
+}
diff --git a/backend/src/main/java/reviewme/highlight/repository/HighlightRepository.java b/backend/src/main/java/reviewme/highlight/repository/HighlightRepository.java
index 74760e09c..2733b2027 100644
--- a/backend/src/main/java/reviewme/highlight/repository/HighlightRepository.java
+++ b/backend/src/main/java/reviewme/highlight/repository/HighlightRepository.java
@@ -2,25 +2,32 @@
import java.util.Collection;
import java.util.List;
-import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.Repository;
import reviewme.highlight.domain.Highlight;
-public interface HighlightRepository extends JpaRepository {
+public interface HighlightRepository extends Repository, HighlightJdbcRepository {
- @Modifying
- @Query("""
- DELETE FROM Highlight h
- WHERE h.answerId IN :answerIds
- """)
- void deleteAllByAnswerIds(Collection answerIds);
+ Highlight save(Highlight highlight);
+
+ boolean existsById(long id);
@Query("""
- SELECT h
- FROM Highlight h
+ SELECT h FROM Highlight h
WHERE h.answerId IN :answerIds
ORDER BY h.lineIndex, h.highlightRange.startIndex ASC
""")
List findAllByAnswerIdsOrderedAsc(Collection answerIds);
+
+ @Modifying
+ @Query("""
+ DELETE FROM Highlight h
+ WHERE h.answerId IN (
+ SELECT a.id FROM Answer a
+ JOIN Review r ON a.reviewId = r.id
+ WHERE r.reviewGroupId = :reviewGroupId AND a.questionId = :questionId
+ )
+ """)
+ void deleteByReviewGroupIdAndQuestionId(long reviewGroupId, long questionId);
}
diff --git a/backend/src/main/java/reviewme/highlight/service/HighlightService.java b/backend/src/main/java/reviewme/highlight/service/HighlightService.java
index 7cb9f9c70..8651bca86 100644
--- a/backend/src/main/java/reviewme/highlight/service/HighlightService.java
+++ b/backend/src/main/java/reviewme/highlight/service/HighlightService.java
@@ -1,7 +1,6 @@
package reviewme.highlight.service;
import java.util.List;
-import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -9,8 +8,7 @@
import reviewme.highlight.repository.HighlightRepository;
import reviewme.highlight.service.dto.HighlightsRequest;
import reviewme.highlight.service.mapper.HighlightMapper;
-import reviewme.highlight.service.validator.HighlightValidator;
-import reviewme.review.repository.AnswerRepository;
+import reviewme.review.service.validator.AnswerValidator;
import reviewme.reviewgroup.domain.ReviewGroup;
@Service
@@ -18,19 +16,18 @@
public class HighlightService {
private final HighlightRepository highlightRepository;
- private final AnswerRepository answerRepository;
- private final HighlightValidator highlightValidator;
private final HighlightMapper highlightMapper;
+ private final AnswerValidator answerValidator;
@Transactional
public void editHighlight(HighlightsRequest highlightsRequest, ReviewGroup reviewGroup) {
- highlightValidator.validate(highlightsRequest, reviewGroup);
- List highlights = highlightMapper.mapToHighlights(highlightsRequest);
-
- Set answerIds = answerRepository.findIdsByQuestionId(highlightsRequest.questionId());
- highlightRepository.deleteAllByAnswerIds(answerIds);
+ List requestedAnswerIds = highlightsRequest.getUniqueAnswerIds();
+ answerValidator.validateQuestionContainsAnswers(highlightsRequest.questionId(), requestedAnswerIds);
+ answerValidator.validateReviewGroupContainsAnswers(reviewGroup, requestedAnswerIds);
+ List highlights = highlightMapper.mapToHighlights(highlightsRequest);
+ highlightRepository.deleteByReviewGroupIdAndQuestionId(reviewGroup.getId(), highlightsRequest.questionId());
highlightRepository.saveAll(highlights);
}
}
diff --git a/backend/src/main/java/reviewme/highlight/service/dto/HighlightRequest.java b/backend/src/main/java/reviewme/highlight/service/dto/HighlightRequest.java
index 673cc8e6a..1371c6959 100644
--- a/backend/src/main/java/reviewme/highlight/service/dto/HighlightRequest.java
+++ b/backend/src/main/java/reviewme/highlight/service/dto/HighlightRequest.java
@@ -4,6 +4,8 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;
+import java.util.stream.Stream;
+import reviewme.highlight.service.mapper.HighlightFragment;
public record HighlightRequest(
@@ -13,4 +15,15 @@ public record HighlightRequest(
@Valid @NotEmpty(message = "하이라이트 된 라인을 입력해주세요.")
List lines
) {
+ public List toFragments() {
+ return lines.stream()
+ .flatMap(this::mapRangesToFragment)
+ .toList();
+ }
+
+ private Stream mapRangesToFragment(HighlightedLineRequest line) {
+ return line.ranges()
+ .stream()
+ .map(range -> new HighlightFragment(answerId, line.index(), range.startIndex(), range.endIndex()));
+ }
}
diff --git a/backend/src/main/java/reviewme/highlight/service/dto/HighlightsRequest.java b/backend/src/main/java/reviewme/highlight/service/dto/HighlightsRequest.java
index b8f26cba6..b1f7f6de3 100644
--- a/backend/src/main/java/reviewme/highlight/service/dto/HighlightsRequest.java
+++ b/backend/src/main/java/reviewme/highlight/service/dto/HighlightsRequest.java
@@ -3,6 +3,7 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.util.List;
+import reviewme.highlight.service.mapper.HighlightFragment;
public record HighlightsRequest(
@@ -20,4 +21,10 @@ public List getUniqueAnswerIds() {
.distinct()
.toList();
}
+
+ public List toFragments() {
+ return highlights.stream()
+ .flatMap(request -> request.toFragments().stream())
+ .toList();
+ }
}
diff --git a/backend/src/main/java/reviewme/highlight/service/exception/SubmittedAnswerAndProvidedAnswerMismatchException.java b/backend/src/main/java/reviewme/highlight/service/exception/SubmittedAnswerAndProvidedAnswerMismatchException.java
deleted file mode 100644
index 0282bd983..000000000
--- a/backend/src/main/java/reviewme/highlight/service/exception/SubmittedAnswerAndProvidedAnswerMismatchException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package reviewme.highlight.service.exception;
-
-import java.util.Collection;
-import lombok.extern.slf4j.Slf4j;
-import reviewme.global.exception.BadRequestException;
-
-@Slf4j
-public class SubmittedAnswerAndProvidedAnswerMismatchException extends BadRequestException {
-
- public SubmittedAnswerAndProvidedAnswerMismatchException(Collection providedAnswerIds,
- Collection submittedAnswerIds) {
- super("제출된 응답이 제공된 응답과 일치하지 않아요.");
- log.info("SubmittedAnswer and providedAnswer mismatch - providedAnswerIds: {}, submittedAnswerIds: {}",
- providedAnswerIds, submittedAnswerIds);
- }
-}
diff --git a/backend/src/main/java/reviewme/highlight/service/mapper/HighlightFragment.java b/backend/src/main/java/reviewme/highlight/service/mapper/HighlightFragment.java
new file mode 100644
index 000000000..33e08f56e
--- /dev/null
+++ b/backend/src/main/java/reviewme/highlight/service/mapper/HighlightFragment.java
@@ -0,0 +1,4 @@
+package reviewme.highlight.service.mapper;
+
+public record HighlightFragment(long answerId, int lineIndex, int startIndex, int endIndex) {
+}
diff --git a/backend/src/main/java/reviewme/highlight/service/mapper/HighlightMapper.java b/backend/src/main/java/reviewme/highlight/service/mapper/HighlightMapper.java
index edbec9013..512546030 100644
--- a/backend/src/main/java/reviewme/highlight/service/mapper/HighlightMapper.java
+++ b/backend/src/main/java/reviewme/highlight/service/mapper/HighlightMapper.java
@@ -1,21 +1,14 @@
package reviewme.highlight.service.mapper;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.highlight.domain.HighlightedLines;
-import reviewme.highlight.domain.HighlightedLine;
import reviewme.highlight.domain.Highlight;
-import reviewme.highlight.domain.HighlightRange;
-import reviewme.highlight.service.dto.HighlightIndexRangeRequest;
-import reviewme.highlight.service.dto.HighlightRequest;
-import reviewme.highlight.service.dto.HighlightedLineRequest;
+import reviewme.highlight.domain.HighlightedLines;
import reviewme.highlight.service.dto.HighlightsRequest;
-import reviewme.review.domain.Answer;
+import reviewme.review.domain.TextAnswer;
import reviewme.review.repository.TextAnswerRepository;
@Component
@@ -25,53 +18,19 @@ public class HighlightMapper {
private final TextAnswerRepository textAnswerRepository;
public List mapToHighlights(HighlightsRequest highlightsRequest) {
- Map answerHighlightLines = textAnswerRepository
+ Map answerIdHighlightedLines = textAnswerRepository
.findAllById(highlightsRequest.getUniqueAnswerIds())
.stream()
- .collect(Collectors.toMap(Answer::getId, answer -> new HighlightedLines(answer.getContent())));
- addIndexRanges(highlightsRequest, answerHighlightLines);
- return mapLinesToHighlights(answerHighlightLines);
- }
-
- private void addIndexRanges(HighlightsRequest highlightsRequest, Map answerHighlightLines) {
- for (HighlightRequest highlightRequest : highlightsRequest.highlights()) {
- HighlightedLines highlightedLines = answerHighlightLines.get(highlightRequest.answerId());
- addIndexRangesForAnswer(highlightRequest, highlightedLines);
- }
- }
-
- private void addIndexRangesForAnswer(HighlightRequest highlightRequest, HighlightedLines highlightedLines) {
- for (HighlightedLineRequest lineRequest : highlightRequest.lines()) {
- int lineIndex = lineRequest.index();
- for (HighlightIndexRangeRequest rangeRequest : lineRequest.ranges()) {
- highlightedLines.addRange(lineIndex, rangeRequest.startIndex(), rangeRequest.endIndex());
- }
- }
- }
-
- private List mapLinesToHighlights(Map answerHighlightLines) {
- List highlights = new ArrayList<>();
- for (Entry answerHighlightLine : answerHighlightLines.entrySet()) {
- createHighlightsForAnswer(answerHighlightLine, highlights);
- }
- return highlights;
- }
+ .collect(Collectors.toMap(TextAnswer::getId, answer -> new HighlightedLines(answer.getContent())));
- private void createHighlightsForAnswer(Entry answerHighlightLine,
- List highlights) {
- long answerId = answerHighlightLine.getKey();
- List highlightedLines = answerHighlightLine.getValue().getLines();
-
- for (int lineIndex = 0; lineIndex < highlightedLines.size(); lineIndex++) {
- createHighlightForLine(highlightedLines, lineIndex, answerId, highlights);
+ for (HighlightFragment fragment : highlightsRequest.toFragments()) {
+ HighlightedLines highlightedLines = answerIdHighlightedLines.get(fragment.answerId());
+ highlightedLines.addRange(fragment.lineIndex(), fragment.startIndex(), fragment.endIndex());
}
- }
- private void createHighlightForLine(List highlightedLines, int lineIndex, long answerId,
- List highlights) {
- for (HighlightRange range : highlightedLines.get(lineIndex).getRanges()) {
- Highlight highlight = new Highlight(answerId, lineIndex, range);
- highlights.add(highlight);
- }
+ return answerIdHighlightedLines.entrySet()
+ .stream()
+ .flatMap(entry -> entry.getValue().toHighlights(entry.getKey()).stream())
+ .toList();
}
}
diff --git a/backend/src/main/java/reviewme/highlight/service/validator/HighlightValidator.java b/backend/src/main/java/reviewme/highlight/service/validator/HighlightValidator.java
deleted file mode 100644
index e05f0f9df..000000000
--- a/backend/src/main/java/reviewme/highlight/service/validator/HighlightValidator.java
+++ /dev/null
@@ -1,41 +0,0 @@
-
-package reviewme.highlight.service.validator;
-
-import java.util.List;
-import java.util.Set;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Component;
-import reviewme.highlight.service.dto.HighlightsRequest;
-import reviewme.highlight.service.exception.SubmittedAnswerAndProvidedAnswerMismatchException;
-import reviewme.review.repository.AnswerRepository;
-import reviewme.reviewgroup.domain.ReviewGroup;
-
-@Component
-@RequiredArgsConstructor
-public class HighlightValidator {
-
- private final AnswerRepository answerRepository;
-
- public void validate(HighlightsRequest request, ReviewGroup reviewGroup) {
- validateQuestionContainsAnswer(request);
- validateReviewGroupContainsAnswer(request, reviewGroup);
- }
-
- private void validateQuestionContainsAnswer(HighlightsRequest request) {
- Set providedAnswerIds = answerRepository.findIdsByQuestionId(request.questionId());
- List submittedAnswerIds = request.getUniqueAnswerIds();
-
- if (!providedAnswerIds.containsAll(submittedAnswerIds)) {
- throw new SubmittedAnswerAndProvidedAnswerMismatchException(providedAnswerIds, submittedAnswerIds);
- }
- }
-
- private void validateReviewGroupContainsAnswer(HighlightsRequest request, ReviewGroup reviewGroup) {
- Set providedAnswerIds = answerRepository.findIdsByReviewGroupId(reviewGroup.getId());
- List submittedAnswerIds = request.getUniqueAnswerIds();
-
- if (!providedAnswerIds.containsAll(submittedAnswerIds)) {
- throw new SubmittedAnswerAndProvidedAnswerMismatchException(providedAnswerIds, submittedAnswerIds);
- }
- }
-}
diff --git a/backend/src/main/java/reviewme/member/controller/MemberController.java b/backend/src/main/java/reviewme/member/controller/MemberController.java
new file mode 100644
index 000000000..662ccf88c
--- /dev/null
+++ b/backend/src/main/java/reviewme/member/controller/MemberController.java
@@ -0,0 +1,21 @@
+package reviewme.member.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reviewme.member.service.MemberService;
+import reviewme.member.service.dto.ProfileResponse;
+
+@RestController
+@RequiredArgsConstructor
+public class MemberController {
+
+ private final MemberService memberService;
+
+ @GetMapping("/v2/members/profile")
+ public ResponseEntity getProfile() {
+ ProfileResponse response = memberService.getProfile();
+ return ResponseEntity.ok(response);
+ }
+}
diff --git a/backend/src/main/java/reviewme/member/service/MemberService.java b/backend/src/main/java/reviewme/member/service/MemberService.java
new file mode 100644
index 000000000..ef828b848
--- /dev/null
+++ b/backend/src/main/java/reviewme/member/service/MemberService.java
@@ -0,0 +1,12 @@
+package reviewme.member.service;
+
+import org.springframework.stereotype.Service;
+import reviewme.member.service.dto.ProfileResponse;
+
+@Service
+public class MemberService {
+
+ public ProfileResponse getProfile() {
+ return null;
+ }
+}
diff --git a/backend/src/main/java/reviewme/member/service/dto/ProfileResponse.java b/backend/src/main/java/reviewme/member/service/dto/ProfileResponse.java
new file mode 100644
index 000000000..5ec6900cd
--- /dev/null
+++ b/backend/src/main/java/reviewme/member/service/dto/ProfileResponse.java
@@ -0,0 +1,7 @@
+package reviewme.member.service.dto;
+
+public record ProfileResponse(
+ String nickname,
+ String profileImageUrl
+) {
+}
diff --git a/backend/src/main/java/reviewme/question/domain/OptionGroup.java b/backend/src/main/java/reviewme/question/domain/OptionGroup.java
deleted file mode 100644
index 61aa3d23a..000000000
--- a/backend/src/main/java/reviewme/question/domain/OptionGroup.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package reviewme.question.domain;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
-import lombok.AccessLevel;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-
-@Entity
-@Table(name = "option_group")
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-@EqualsAndHashCode(of = "id")
-@Getter
-public class OptionGroup {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(name = "question_id", nullable = false)
- private long questionId;
-
- @Column(name = "min_selection_count", nullable = false)
- private int minSelectionCount;
-
- @Column(name = "max_selection_count", nullable = false)
- private int maxSelectionCount;
-
- public OptionGroup(long questionId, int minSelectionCount, int maxSelectionCount) {
- this.questionId = questionId;
- this.minSelectionCount = minSelectionCount;
- this.maxSelectionCount = maxSelectionCount;
- }
-}
diff --git a/backend/src/main/java/reviewme/question/repository/QuestionRepository.java b/backend/src/main/java/reviewme/question/repository/QuestionRepository.java
deleted file mode 100644
index 9db137d25..000000000
--- a/backend/src/main/java/reviewme/question/repository/QuestionRepository.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package reviewme.question.repository;
-
-import java.util.List;
-import java.util.Set;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.stereotype.Repository;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-
-@Repository
-public interface QuestionRepository extends JpaRepository {
-
- @Query("""
- SELECT q.id FROM Question q
- JOIN SectionQuestion sq
- ON q.id = sq.questionId
- JOIN TemplateSection ts
- ON sq.sectionId = ts.sectionId
- WHERE ts.templateId = :templateId
- """)
- Set findAllQuestionIdByTemplateId(long templateId);
-
- @Query("""
- SELECT q FROM Question q
- JOIN SectionQuestion sq
- ON q.id = sq.questionId
- JOIN TemplateSection ts
- ON sq.sectionId = ts.sectionId
- WHERE ts.templateId = :templateId
- """)
- List findAllByTemplatedId(long templateId);
-
- @Query("""
- SELECT q FROM Question q
- JOIN SectionQuestion sq ON q.id = sq.questionId
- WHERE sq.sectionId = :sectionId
- ORDER BY q.position
- """)
- List findAllBySectionIdOrderByPosition(long sectionId);
-
- @Query("""
- SELECT o FROM OptionItem o
- JOIN OptionGroup og ON o.optionGroupId = og.id
- WHERE og.questionId = :questionId
- ORDER BY o.position
- """)
- List findAllOptionItemsByIdOrderByPosition(long questionId);
-}
diff --git a/backend/src/main/java/reviewme/review/controller/ReviewController.java b/backend/src/main/java/reviewme/review/controller/ReviewController.java
index 1b31af214..0689e6fa0 100644
--- a/backend/src/main/java/reviewme/review/controller/ReviewController.java
+++ b/backend/src/main/java/reviewme/review/controller/ReviewController.java
@@ -18,8 +18,9 @@
import reviewme.review.service.dto.request.ReviewRegisterRequest;
import reviewme.review.service.dto.response.detail.ReviewDetailResponse;
import reviewme.review.service.dto.response.gathered.ReviewsGatheredBySectionResponse;
-import reviewme.review.service.dto.response.list.ReceivedReviewsResponse;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageResponse;
import reviewme.review.service.dto.response.list.ReceivedReviewsSummaryResponse;
+import reviewme.review.service.dto.response.list.AuthoredReviewsResponse;
import reviewme.reviewgroup.controller.ReviewGroupSession;
import reviewme.reviewgroup.domain.ReviewGroup;
@@ -35,17 +36,18 @@ public class ReviewController {
@PostMapping("/v2/reviews")
public ResponseEntity createReview(@Valid @RequestBody ReviewRegisterRequest request) {
+ // 회원 세션 추후 추가해야 함
long savedReviewId = reviewRegisterService.registerReview(request);
return ResponseEntity.created(URI.create("/reviews/" + savedReviewId)).build();
}
- @GetMapping("/v2/reviews")
- public ResponseEntity findReceivedReviews(
+ @GetMapping("/v2/reviews/received")
+ public ResponseEntity findReceivedReviews(
@RequestParam(required = false) Long lastReviewId,
@RequestParam(required = false) Integer size,
@ReviewGroupSession ReviewGroup reviewGroup
) {
- ReceivedReviewsResponse response = reviewListLookupService.getReceivedReviews(lastReviewId, size, reviewGroup);
+ ReceivedReviewPageResponse response = reviewListLookupService.getReceivedReviews(lastReviewId, size, reviewGroup);
return ResponseEntity.ok(response);
}
@@ -75,4 +77,15 @@ public ResponseEntity getReceivedReviewsBySect
reviewGatheredLookupService.getReceivedReviewsBySectionId(reviewGroup, sectionId);
return ResponseEntity.ok(response);
}
+
+ @GetMapping("/v2/reviews/authored")
+ public ResponseEntity findAuthoredReviews(
+ @RequestParam(required = false) Long lastReviewId,
+ @RequestParam(required = false) Integer size
+// @MemberSession Member member
+ // TODO: 세션을 활용한 권한 체계에 따른 추가 조치 필요
+ ) {
+ AuthoredReviewsResponse response = reviewListLookupService.getAuthoredReviews(lastReviewId, size);
+ return ResponseEntity.ok(response);
+ }
}
diff --git a/backend/src/main/java/reviewme/review/domain/exception/MissingTextAnswerForQuestionException.java b/backend/src/main/java/reviewme/review/domain/exception/MissingTextAnswerForQuestionException.java
deleted file mode 100644
index 674dce41c..000000000
--- a/backend/src/main/java/reviewme/review/domain/exception/MissingTextAnswerForQuestionException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package reviewme.review.domain.exception;
-
-import lombok.extern.slf4j.Slf4j;
-import reviewme.global.exception.DataInconsistencyException;
-
-@Slf4j
-public class MissingTextAnswerForQuestionException extends DataInconsistencyException {
-
- public MissingTextAnswerForQuestionException(long questionId) {
- super("서버 내부에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.");
- log.error("The question is a text question but text answer not found for questionId: {}", questionId, this);
- }
-}
diff --git a/backend/src/main/java/reviewme/review/repository/AnswerRepository.java b/backend/src/main/java/reviewme/review/repository/AnswerRepository.java
index ea793623a..5b18ab9f2 100644
--- a/backend/src/main/java/reviewme/review/repository/AnswerRepository.java
+++ b/backend/src/main/java/reviewme/review/repository/AnswerRepository.java
@@ -15,7 +15,7 @@ public interface AnswerRepository extends JpaRepository {
SELECT a FROM Answer a
JOIN Review r ON a.reviewId = r.id
WHERE r.reviewGroupId = :reviewGroupId AND a.questionId IN :questionIds
- ORDER BY r.createdAt DESC
+ ORDER BY r.createdAt DESC, r.id DESC
LIMIT :limit
""")
List findReceivedAnswersByQuestionIds(long reviewGroupId, Collection questionIds, int limit);
diff --git a/backend/src/main/java/reviewme/review/repository/ReviewRepository.java b/backend/src/main/java/reviewme/review/repository/ReviewRepository.java
index 3a0600ad9..90119fa0b 100644
--- a/backend/src/main/java/reviewme/review/repository/ReviewRepository.java
+++ b/backend/src/main/java/reviewme/review/repository/ReviewRepository.java
@@ -12,7 +12,7 @@ public interface ReviewRepository extends JpaRepository {
@Query("""
SELECT r FROM Review r
WHERE r.reviewGroupId = :reviewGroupId
- ORDER BY r.createdAt DESC
+ ORDER BY r.createdAt DESC, r.id DESC
""")
List findAllByGroupId(long reviewGroupId);
diff --git a/backend/src/main/java/reviewme/review/service/ReviewGatheredLookupService.java b/backend/src/main/java/reviewme/review/service/ReviewGatheredLookupService.java
index 703348c9e..82de0ab16 100644
--- a/backend/src/main/java/reviewme/review/service/ReviewGatheredLookupService.java
+++ b/backend/src/main/java/reviewme/review/service/ReviewGatheredLookupService.java
@@ -9,8 +9,8 @@
import org.springframework.transaction.annotation.Transactional;
import reviewme.highlight.domain.Highlight;
import reviewme.highlight.repository.HighlightRepository;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
+import reviewme.template.domain.Question;
+import reviewme.template.repository.QuestionRepository;
import reviewme.review.domain.Answer;
import reviewme.review.repository.AnswerRepository;
import reviewme.review.service.dto.response.gathered.ReviewsGatheredBySectionResponse;
diff --git a/backend/src/main/java/reviewme/review/service/ReviewListLookupService.java b/backend/src/main/java/reviewme/review/service/ReviewListLookupService.java
index d576e9eeb..933e5a219 100644
--- a/backend/src/main/java/reviewme/review/service/ReviewListLookupService.java
+++ b/backend/src/main/java/reviewme/review/service/ReviewListLookupService.java
@@ -5,8 +5,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reviewme.review.repository.ReviewRepository;
-import reviewme.review.service.dto.response.list.ReceivedReviewsResponse;
-import reviewme.review.service.dto.response.list.ReviewListElementResponse;
+import reviewme.review.service.dto.response.list.AuthoredReviewsResponse;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageResponse;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageElementResponse;
import reviewme.review.service.mapper.ReviewListMapper;
import reviewme.reviewgroup.domain.ReviewGroup;
@@ -18,30 +19,35 @@ public class ReviewListLookupService {
private final ReviewListMapper reviewListMapper;
@Transactional(readOnly = true)
- public ReceivedReviewsResponse getReceivedReviews(Long lastReviewId, Integer size, ReviewGroup reviewGroup) {
+ public ReceivedReviewPageResponse getReceivedReviews(Long lastReviewId, Integer size, ReviewGroup reviewGroup) {
PageSize pageSize = new PageSize(size);
- List reviewListResponse
+ List reviewListResponse
= reviewListMapper.mapToReviewList(reviewGroup, lastReviewId, pageSize.getSize());
long newLastReviewId = calculateLastReviewId(reviewListResponse);
boolean isLastPage = isLastPage(reviewListResponse, reviewGroup);
- return new ReceivedReviewsResponse(
+ return new ReceivedReviewPageResponse(
reviewGroup.getReviewee(), reviewGroup.getProjectName(), newLastReviewId, isLastPage, reviewListResponse
);
}
- private long calculateLastReviewId(List elements) {
+ public AuthoredReviewsResponse getAuthoredReviews(Long lastReviewId, Integer size) {
+ // TODO: 생성일자 최신순 정렬
+ return null;
+ }
+
+ private long calculateLastReviewId(List elements) {
if (elements.isEmpty()) {
return 0;
}
return elements.get(elements.size() - 1).reviewId();
}
- private boolean isLastPage(List elements, ReviewGroup reviewGroup) {
+ private boolean isLastPage(List elements, ReviewGroup reviewGroup) {
if (elements.isEmpty()) {
return true;
}
- ReviewListElementResponse lastReviewResponse = elements.get(elements.size() - 1);
+ ReceivedReviewPageElementResponse lastReviewResponse = elements.get(elements.size() - 1);
return !reviewRepository.existsOlderReviewInGroup(
reviewGroup.getId(), lastReviewResponse.reviewId(), lastReviewResponse.createdAt());
}
diff --git a/backend/src/main/java/reviewme/review/service/ReviewRegisterService.java b/backend/src/main/java/reviewme/review/service/ReviewRegisterService.java
index 966eaa602..87e5f8538 100644
--- a/backend/src/main/java/reviewme/review/service/ReviewRegisterService.java
+++ b/backend/src/main/java/reviewme/review/service/ReviewRegisterService.java
@@ -1,8 +1,6 @@
package reviewme.review.service;
import lombok.RequiredArgsConstructor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reviewme.review.domain.Review;
@@ -15,8 +13,6 @@
@RequiredArgsConstructor
public class ReviewRegisterService {
- private static final Logger log = LoggerFactory.getLogger(ReviewRegisterService.class);
-
private final ReviewMapper reviewMapper;
private final ReviewValidator reviewValidator;
private final ReviewRepository reviewRepository;
diff --git a/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java b/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java
index 60233be2d..85d89cd7e 100644
--- a/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java
+++ b/backend/src/main/java/reviewme/review/service/dto/request/ReviewAnswerRequest.java
@@ -15,11 +15,12 @@ public record ReviewAnswerRequest(
@Nullable
String text
) {
- public boolean hasTextAnswer() {
- return text != null && !text.isEmpty();
+
+ public boolean hasNoText() {
+ return text == null || text.isBlank();
}
- public boolean hasCheckboxAnswer() {
- return selectedOptionIds != null && !selectedOptionIds.isEmpty();
+ public boolean hasNoSelectedOptions() {
+ return selectedOptionIds == null || selectedOptionIds.isEmpty();
}
}
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionGroupAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionGroupAnswerResponse.java
deleted file mode 100644
index 894dbaae8..000000000
--- a/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionGroupAnswerResponse.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package reviewme.review.service.dto.response.detail;
-
-import java.util.List;
-
-public record OptionGroupAnswerResponse(
- long optionGroupId,
- long minCount,
- long maxCount,
- List options
-) {
-}
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java
index 6bd424f5f..d5c9ae174 100644
--- a/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java
+++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/OptionItemAnswerResponse.java
@@ -2,7 +2,6 @@
public record OptionItemAnswerResponse(
long optionId,
- String content,
- boolean isChecked
+ String content
) {
}
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java
index 000eb83c8..761a86f76 100644
--- a/backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java
+++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/QuestionAnswerResponse.java
@@ -1,14 +1,15 @@
package reviewme.review.service.dto.response.detail;
import jakarta.annotation.Nullable;
-import reviewme.question.domain.QuestionType;
+import java.util.List;
+import reviewme.template.domain.QuestionType;
public record QuestionAnswerResponse(
long questionId,
boolean required,
QuestionType questionType,
- String content,
- @Nullable OptionGroupAnswerResponse optionGroup,
+ String questionContents,
+ @Nullable List options,
@Nullable String answer
) {
}
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java
index eb45bddb2..ec330b76f 100644
--- a/backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java
+++ b/backend/src/main/java/reviewme/review/service/dto/response/detail/SectionAnswerResponse.java
@@ -5,10 +5,6 @@
public record SectionAnswerResponse(
long sectionId,
String header,
- List questions
+ List reviews
) {
-
- public boolean hasAnsweredQuestion() {
- return !questions.isEmpty();
- }
}
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/gathered/SimpleQuestionResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/gathered/SimpleQuestionResponse.java
index e16df25e6..d7f1647d9 100644
--- a/backend/src/main/java/reviewme/review/service/dto/response/gathered/SimpleQuestionResponse.java
+++ b/backend/src/main/java/reviewme/review/service/dto/response/gathered/SimpleQuestionResponse.java
@@ -1,6 +1,6 @@
package reviewme.review.service.dto.response.gathered;
-import reviewme.question.domain.QuestionType;
+import reviewme.template.domain.QuestionType;
public record SimpleQuestionResponse(
long id,
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/list/AuthoredReviewElementResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/list/AuthoredReviewElementResponse.java
new file mode 100644
index 000000000..282f6dbd3
--- /dev/null
+++ b/backend/src/main/java/reviewme/review/service/dto/response/list/AuthoredReviewElementResponse.java
@@ -0,0 +1,14 @@
+package reviewme.review.service.dto.response.list;
+
+import java.time.LocalDate;
+import java.util.List;
+
+public record AuthoredReviewElementResponse(
+ long reviewId,
+ String revieweeName,
+ String projectName,
+ LocalDate createdAt,
+ String contentPreview,
+ List categories
+) {
+}
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/list/AuthoredReviewsResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/list/AuthoredReviewsResponse.java
new file mode 100644
index 000000000..7d712a31c
--- /dev/null
+++ b/backend/src/main/java/reviewme/review/service/dto/response/list/AuthoredReviewsResponse.java
@@ -0,0 +1,10 @@
+package reviewme.review.service.dto.response.list;
+
+import java.util.List;
+
+public record AuthoredReviewsResponse(
+ List reviews,
+ long lastReviewId,
+ boolean isLastPage
+) {
+}
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/list/ReviewListElementResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewPageElementResponse.java
similarity index 83%
rename from backend/src/main/java/reviewme/review/service/dto/response/list/ReviewListElementResponse.java
rename to backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewPageElementResponse.java
index 07aa32c9f..5aa7c36e5 100644
--- a/backend/src/main/java/reviewme/review/service/dto/response/list/ReviewListElementResponse.java
+++ b/backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewPageElementResponse.java
@@ -3,7 +3,7 @@
import java.time.LocalDate;
import java.util.List;
-public record ReviewListElementResponse(
+public record ReceivedReviewPageElementResponse(
long reviewId,
LocalDate createdAt,
String contentPreview,
diff --git a/backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewsResponse.java b/backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewPageResponse.java
similarity index 66%
rename from backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewsResponse.java
rename to backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewPageResponse.java
index eace5cd50..35042e0d5 100644
--- a/backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewsResponse.java
+++ b/backend/src/main/java/reviewme/review/service/dto/response/list/ReceivedReviewPageResponse.java
@@ -2,11 +2,11 @@
import java.util.List;
-public record ReceivedReviewsResponse(
+public record ReceivedReviewPageResponse(
String revieweeName,
String projectName,
long lastReviewId,
boolean isLastPage,
- List reviews
+ List reviews
) {
}
diff --git a/backend/src/main/java/reviewme/review/service/exception/AnswerNotFoundByIdException.java b/backend/src/main/java/reviewme/review/service/exception/AnswerNotFoundByIdException.java
deleted file mode 100644
index aef381ffc..000000000
--- a/backend/src/main/java/reviewme/review/service/exception/AnswerNotFoundByIdException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package reviewme.review.service.exception;
-
-import lombok.extern.slf4j.Slf4j;
-import reviewme.global.exception.NotFoundException;
-
-@Slf4j
-public class AnswerNotFoundByIdException extends NotFoundException {
-
- public AnswerNotFoundByIdException(long answerId) {
- super("답변을 찾을 수 없어요.");
- log.info("Answer not found by id - answerId: {}", answerId);
- }
-}
diff --git a/backend/src/main/java/reviewme/review/service/exception/CheckBoxAnswerIncludedTextException.java b/backend/src/main/java/reviewme/review/service/exception/CheckBoxAnswerIncludedTextException.java
deleted file mode 100644
index a563acbf0..000000000
--- a/backend/src/main/java/reviewme/review/service/exception/CheckBoxAnswerIncludedTextException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package reviewme.review.service.exception;
-
-import lombok.extern.slf4j.Slf4j;
-import reviewme.global.exception.BadRequestException;
-
-@Slf4j
-public class CheckBoxAnswerIncludedTextException extends BadRequestException {
-
- public CheckBoxAnswerIncludedTextException(long questionId) {
- super("체크박스형 응답은 텍스트를 포함할 수 없어요.");
- log.info("CheckBox type answer cannot have option items - questionId: {}", questionId);
- }
-}
diff --git a/backend/src/main/java/reviewme/review/service/exception/GatheredAnswersTypeNonUniformException.java b/backend/src/main/java/reviewme/review/service/exception/GatheredAnswersTypeNonUniformException.java
index 3d13fd987..fd8293f3b 100644
--- a/backend/src/main/java/reviewme/review/service/exception/GatheredAnswersTypeNonUniformException.java
+++ b/backend/src/main/java/reviewme/review/service/exception/GatheredAnswersTypeNonUniformException.java
@@ -7,7 +7,7 @@
public class GatheredAnswersTypeNonUniformException extends DataInconsistencyException {
public GatheredAnswersTypeNonUniformException(Throwable cause) {
- super("서버 내부 오류가 발생했습니다.");
+ super("서버 내부 오류가 발생했어요.");
log.error("The types of answers to questions are not uniform.", cause);
}
}
diff --git a/backend/src/main/java/reviewme/review/service/exception/InvalidTextAnswerLengthException.java b/backend/src/main/java/reviewme/review/service/exception/InvalidTextAnswerLengthException.java
index 01c02ceb7..314f72673 100644
--- a/backend/src/main/java/reviewme/review/service/exception/InvalidTextAnswerLengthException.java
+++ b/backend/src/main/java/reviewme/review/service/exception/InvalidTextAnswerLengthException.java
@@ -8,7 +8,7 @@ public class InvalidTextAnswerLengthException extends BadRequestException {
public InvalidTextAnswerLengthException(long questionId, int answerLength, int minLength, int maxLength) {
super("답변의 길이는 %d자 이상 %d자 이하여야 해요.".formatted(minLength, maxLength));
- log.warn("AnswerLength is out of bound - questionId: {}, answerLength: {}, minLength: {}, maxLength: {}",
+ log.info("AnswerLength is out of bound - questionId: {}, answerLength: {}, minLength: {}, maxLength: {}",
questionId, answerLength, minLength, maxLength, this);
}
diff --git a/backend/src/main/java/reviewme/review/service/exception/QuestionNotContainingAnswersException.java b/backend/src/main/java/reviewme/review/service/exception/QuestionNotContainingAnswersException.java
new file mode 100644
index 000000000..3a7740787
--- /dev/null
+++ b/backend/src/main/java/reviewme/review/service/exception/QuestionNotContainingAnswersException.java
@@ -0,0 +1,15 @@
+package reviewme.review.service.exception;
+
+import java.util.Collection;
+import lombok.extern.slf4j.Slf4j;
+import reviewme.global.exception.ReviewMeException;
+
+@Slf4j
+public class QuestionNotContainingAnswersException extends ReviewMeException {
+
+ public QuestionNotContainingAnswersException(long questionId, Collection providedAnswerIds) {
+ super("질문에 속하지 않는 답변이예요.");
+ log.info("Question not containing provided answers - questionId: {}, providedAnswerIds: {}",
+ questionId, providedAnswerIds);
+ }
+}
diff --git a/backend/src/main/java/reviewme/review/service/exception/ReviewGroupNotContainingAnswersException.java b/backend/src/main/java/reviewme/review/service/exception/ReviewGroupNotContainingAnswersException.java
new file mode 100644
index 000000000..7f641512f
--- /dev/null
+++ b/backend/src/main/java/reviewme/review/service/exception/ReviewGroupNotContainingAnswersException.java
@@ -0,0 +1,15 @@
+package reviewme.review.service.exception;
+
+import java.util.Collection;
+import lombok.extern.slf4j.Slf4j;
+import reviewme.global.exception.ReviewMeException;
+
+@Slf4j
+public class ReviewGroupNotContainingAnswersException extends ReviewMeException {
+
+ public ReviewGroupNotContainingAnswersException(long reviewGroupId, Collection providedAnswerIds) {
+ super("리뷰 그룹에 속하지 않는 답변이예요.");
+ log.info("ReviewGroup not containing provided answers - reviewGroupId: {}, providedAnswerIds: {}",
+ reviewGroupId, providedAnswerIds);
+ }
+}
diff --git a/backend/src/main/java/reviewme/review/service/exception/SectionNotFoundInTemplateException.java b/backend/src/main/java/reviewme/review/service/exception/SectionNotFoundInTemplateException.java
index 9941c8c8a..6b4e7eeb8 100644
--- a/backend/src/main/java/reviewme/review/service/exception/SectionNotFoundInTemplateException.java
+++ b/backend/src/main/java/reviewme/review/service/exception/SectionNotFoundInTemplateException.java
@@ -7,7 +7,7 @@
public class SectionNotFoundInTemplateException extends NotFoundException {
public SectionNotFoundInTemplateException(long sectionId, long templateId) {
- super("섹션 정보를 찾을 수 없습니다.");
+ super("섹션 정보를 찾을 수 없어요.");
log.info("Section not found in template - sectionId: {}, templateId: {}", sectionId, templateId, this);
}
}
diff --git a/backend/src/main/java/reviewme/review/service/exception/SubmittedQuestionAndProvidedQuestionMismatchException.java b/backend/src/main/java/reviewme/review/service/exception/SubmittedQuestionAndProvidedQuestionMismatchException.java
index 1924b1cf5..97b0f77d9 100644
--- a/backend/src/main/java/reviewme/review/service/exception/SubmittedQuestionAndProvidedQuestionMismatchException.java
+++ b/backend/src/main/java/reviewme/review/service/exception/SubmittedQuestionAndProvidedQuestionMismatchException.java
@@ -1,7 +1,6 @@
package reviewme.review.service.exception;
import java.util.Collection;
-import java.util.List;
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;
@@ -16,9 +15,4 @@ public SubmittedQuestionAndProvidedQuestionMismatchException(Collection su
submittedQuestionIds, providedQuestionIds, this
);
}
-
- public SubmittedQuestionAndProvidedQuestionMismatchException(long submittedQuestionId,
- Collection providedQuestionIds) {
- this(List.of(submittedQuestionId), providedQuestionIds);
- }
}
diff --git a/backend/src/main/java/reviewme/review/service/exception/TextAnswerIncludedOptionItemException.java b/backend/src/main/java/reviewme/review/service/exception/TextAnswerIncludedOptionItemException.java
deleted file mode 100644
index ba9310ee6..000000000
--- a/backend/src/main/java/reviewme/review/service/exception/TextAnswerIncludedOptionItemException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package reviewme.review.service.exception;
-
-import lombok.extern.slf4j.Slf4j;
-import reviewme.global.exception.BadRequestException;
-
-@Slf4j
-public class TextAnswerIncludedOptionItemException extends BadRequestException {
-
- public TextAnswerIncludedOptionItemException(long questionId) {
- super("텍스트형 응답은 옵션 항목을 포함할 수 없어요.");
- log.info("Text type answer cannot have option items - questionId: {}", questionId);
- }
-}
diff --git a/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java b/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java
index 7b3cbb631..87ee4c511 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/AnswerMapper.java
@@ -1,8 +1,8 @@
package reviewme.review.service.mapper;
-import reviewme.question.domain.QuestionType;
import reviewme.review.domain.Answer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
+import reviewme.template.domain.QuestionType;
public interface AnswerMapper {
diff --git a/backend/src/main/java/reviewme/review/service/mapper/AnswerMapperFactory.java b/backend/src/main/java/reviewme/review/service/mapper/AnswerMapperFactory.java
index 6dc804547..624c3ba81 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/AnswerMapperFactory.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/AnswerMapperFactory.java
@@ -3,7 +3,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.QuestionType;
+import reviewme.template.domain.QuestionType;
@Component
@RequiredArgsConstructor
diff --git a/backend/src/main/java/reviewme/review/service/mapper/CheckboxAnswerMapper.java b/backend/src/main/java/reviewme/review/service/mapper/CheckboxAnswerMapper.java
index 3648e32f6..2829890cd 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/CheckboxAnswerMapper.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/CheckboxAnswerMapper.java
@@ -1,10 +1,9 @@
package reviewme.review.service.mapper;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.QuestionType;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
-import reviewme.review.service.exception.CheckBoxAnswerIncludedTextException;
+import reviewme.template.domain.QuestionType;
@Component
public class CheckboxAnswerMapper implements AnswerMapper {
@@ -16,8 +15,8 @@ public boolean supports(QuestionType questionType) {
@Override
public CheckboxAnswer mapToAnswer(ReviewAnswerRequest answerRequest) {
- if (answerRequest.text() != null) {
- throw new CheckBoxAnswerIncludedTextException(answerRequest.questionId());
+ if (answerRequest.hasNoSelectedOptions()) {
+ return null;
}
return new CheckboxAnswer(answerRequest.questionId(), answerRequest.selectedOptionIds());
}
diff --git a/backend/src/main/java/reviewme/review/service/mapper/ReviewDetailMapper.java b/backend/src/main/java/reviewme/review/service/mapper/ReviewDetailMapper.java
index 7121d99b5..3f5e8758a 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/ReviewDetailMapper.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/ReviewDetailMapper.java
@@ -3,61 +3,60 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.CheckboxAnswerSelectedOption;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
-import reviewme.review.service.dto.response.detail.OptionGroupAnswerResponse;
import reviewme.review.service.dto.response.detail.OptionItemAnswerResponse;
import reviewme.review.service.dto.response.detail.QuestionAnswerResponse;
import reviewme.review.service.dto.response.detail.ReviewDetailResponse;
import reviewme.review.service.dto.response.detail.SectionAnswerResponse;
import reviewme.reviewgroup.domain.ReviewGroup;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
import reviewme.template.domain.Section;
-import reviewme.template.repository.SectionRepository;
+import reviewme.template.domain.Template;
+import reviewme.template.repository.TemplateRepository;
+import reviewme.template.service.exception.TemplateNotFoundByReviewGroupException;
@Component
@RequiredArgsConstructor
public class ReviewDetailMapper {
- private final SectionRepository sectionRepository;
- private final QuestionRepository questionRepository;
- private final OptionGroupRepository optionGroupRepository;
- private final OptionItemRepository optionItemRepository;
+ private final TemplateRepository templateRepository;
+ /*
+ TODO:
+ 조회 전용 로직을 만드는 게 좋겠다, Template + 리뷰 정보를 한 번에 내려줘야 한다.
+ Template에서 정보를 가져오는 건 쉽다 (연관관계 있음), 리뷰 관련 정보를 가져와서 어떻게 섞을지 고민하자.
+ */
public ReviewDetailResponse mapToReviewDetailResponse(Review review, ReviewGroup reviewGroup) {
- long templateId = review.getTemplateId();
+ Template template = templateRepository.findById(reviewGroup.getTemplateId())
+ .orElseThrow(() -> new TemplateNotFoundByReviewGroupException(reviewGroup.getId(),
+ reviewGroup.getTemplateId()));
- List sections = sectionRepository.findAllByTemplateId(templateId);
- List questions = questionRepository.findAllByTemplatedId(templateId);
- List questionIds = questions.stream()
- .map(Question::getId)
+ List sections = template.getSections();
+ List questions = sections.stream()
+ .flatMap(section -> section.getQuestions().stream())
.toList();
- Map optionGroupsByQuestion = optionGroupRepository.findAllByQuestionIds(questionIds)
- .stream()
- .collect(Collectors.toMap(OptionGroup::getQuestionId, Function.identity()));
- Map> optionItemsByOptionGroup = optionItemRepository.findAllByQuestionIds(questionIds)
- .stream()
- .collect(Collectors.groupingBy(OptionItem::getOptionGroupId));
+ Map optionGroupsByQuestion = questions.stream()
+ .filter(Question::isCheckbox)
+ .collect(Collectors.toMap(Question::getId, Question::getOptionGroup));
+ Map> optionItemsByOptionGroup = optionGroupsByQuestion.values().stream()
+ .collect(Collectors.toMap(OptionGroup::getId, OptionGroup::getOptionItems));
List sectionResponses = sections.stream()
.map(section -> mapToSectionResponse(review, section, questions,
optionGroupsByQuestion, optionItemsByOptionGroup))
- .filter(sectionResponse -> !sectionResponse.questions().isEmpty())
+ .filter(sectionResponse -> !sectionResponse.reviews().isEmpty())
.toList();
return new ReviewDetailResponse(
- templateId,
+ template.getId(),
reviewGroup.getReviewee(),
reviewGroup.getProjectName(),
review.getCreatedDate(),
@@ -70,7 +69,7 @@ private SectionAnswerResponse mapToSectionResponse(Review review, Section sectio
Map optionGroupsByQuestion,
Map> optionItemsByOptionGroup) {
List questionResponses = questions.stream()
- .filter(question -> section.containsQuestionId(question.getId()))
+ .filter(section::contains)
.filter(question -> review.hasAnsweredQuestion(question.getId()))
.map(question -> mapToQuestionResponse(
review, question, optionGroupsByQuestion, optionItemsByOptionGroup)
@@ -86,7 +85,7 @@ private SectionAnswerResponse mapToSectionResponse(Review review, Section sectio
private QuestionAnswerResponse mapToQuestionResponse(Review review, Question question,
Map optionGroupsByQuestion,
Map> optionItemsByOptionGroup) {
- if (question.isSelectable()) {
+ if (question.isCheckbox()) {
return mapToCheckboxQuestionResponse(review, question, optionGroupsByQuestion, optionItemsByOptionGroup);
} else {
return mapToTextQuestionResponse(review, question);
@@ -107,22 +106,15 @@ private QuestionAnswerResponse mapToCheckboxQuestionResponse(Review review,
List optionItemResponse = optionItems.stream()
.filter(optionItem -> selectedOptionIds.contains(optionItem.getId()))
- .map(optionItem -> new OptionItemAnswerResponse(optionItem.getId(), optionItem.getContent(), true))
+ .map(optionItem -> new OptionItemAnswerResponse(optionItem.getId(), optionItem.getContent()))
.toList();
- OptionGroupAnswerResponse optionGroupAnswerResponse = new OptionGroupAnswerResponse(
- optionGroup.getId(),
- optionGroup.getMinSelectionCount(),
- optionGroup.getMaxSelectionCount(),
- optionItemResponse
- );
-
return new QuestionAnswerResponse(
question.getId(),
question.isRequired(),
question.getQuestionType(),
question.getContent(),
- optionGroupAnswerResponse,
+ optionItemResponse,
null
);
}
diff --git a/backend/src/main/java/reviewme/review/service/mapper/ReviewGatherMapper.java b/backend/src/main/java/reviewme/review/service/mapper/ReviewGatherMapper.java
index 2a1f4e135..fd6471600 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/ReviewGatherMapper.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/ReviewGatherMapper.java
@@ -7,9 +7,9 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import reviewme.highlight.domain.Highlight;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+import reviewme.template.repository.QuestionRepository;
import reviewme.review.domain.Answer;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.CheckboxAnswerSelectedOption;
@@ -51,7 +51,7 @@ private ReviewsGatheredByQuestionResponse mapToReviewsGatheredByQuestion(Questio
@Nullable
private List mapToTextResponse(Question question, List answers,
List highlights) {
- if (question.isSelectable()) {
+ if (question.isCheckbox()) {
return null;
}
Map> answerIdHighlights = highlights.stream()
@@ -84,7 +84,7 @@ private List mapToHighlightResponse(List highlight
@Nullable
private List mapToVoteResponse(Question question, List answers) {
- if (!question.isSelectable()) {
+ if (!question.isCheckbox()) {
return null;
}
diff --git a/backend/src/main/java/reviewme/review/service/mapper/ReviewListMapper.java b/backend/src/main/java/reviewme/review/service/mapper/ReviewListMapper.java
index aa882802a..de46e8537 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/ReviewListMapper.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/ReviewListMapper.java
@@ -5,16 +5,16 @@
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.OptionType;
-import reviewme.question.repository.OptionItemRepository;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.OptionType;
+import reviewme.template.repository.OptionItemRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.CheckboxAnswerSelectedOption;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
import reviewme.review.repository.ReviewRepository;
import reviewme.review.service.dto.response.list.ReviewCategoryResponse;
-import reviewme.review.service.dto.response.list.ReviewListElementResponse;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageElementResponse;
import reviewme.reviewgroup.domain.ReviewGroup;
@Component
@@ -26,7 +26,7 @@ public class ReviewListMapper {
private final ReviewPreviewGenerator reviewPreviewGenerator = new ReviewPreviewGenerator();
- public List mapToReviewList(ReviewGroup reviewGroup, Long lastReviewId, int size) {
+ public List mapToReviewList(ReviewGroup reviewGroup, Long lastReviewId, int size) {
List categoryOptionItems = optionItemRepository.findAllByOptionType(OptionType.CATEGORY);
return reviewRepository.findByReviewGroupIdWithLimit(reviewGroup.getId(), lastReviewId, size)
.stream()
@@ -34,11 +34,11 @@ public List mapToReviewList(ReviewGroup reviewGroup,
.toList();
}
- private ReviewListElementResponse mapToReviewListElementResponse(Review review,
- List categoryOptionItems) {
+ private ReceivedReviewPageElementResponse mapToReviewListElementResponse(Review review,
+ List categoryOptionItems) {
List categoryResponses = mapToCategoryOptionResponse(review, categoryOptionItems);
- return new ReviewListElementResponse(
+ return new ReceivedReviewPageElementResponse(
review.getId(),
review.getCreatedDate(),
reviewPreviewGenerator.generatePreview(review.getAnswersByType(TextAnswer.class)),
diff --git a/backend/src/main/java/reviewme/review/service/mapper/ReviewMapper.java b/backend/src/main/java/reviewme/review/service/mapper/ReviewMapper.java
index 68ee776b9..0db3362ae 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/ReviewMapper.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/ReviewMapper.java
@@ -8,13 +8,13 @@
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
+import reviewme.template.domain.Question;
+import reviewme.template.repository.QuestionRepository;
import reviewme.review.domain.Answer;
import reviewme.review.domain.Review;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
import reviewme.review.service.dto.request.ReviewRegisterRequest;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.review.service.exception.SubmittedQuestionNotFoundException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
@@ -62,20 +62,10 @@ private List getAnswersByQuestionType(ReviewRegisterRequest request) {
private Answer mapRequestToAnswer(Map questions, ReviewAnswerRequest answerRequest) {
Question question = questions.get(answerRequest.questionId());
-
if (question == null) {
throw new SubmittedQuestionNotFoundException(answerRequest.questionId());
}
- // TODO: 아래 코드를 삭제해야 한다
- if (question.isSelectable() && answerRequest.selectedOptionIds() != null && answerRequest.selectedOptionIds().isEmpty()) {
- return null;
- }
- if (!question.isSelectable() && answerRequest.text() != null && answerRequest.text().isEmpty()) {
- return null;
- }
- // END
-
AnswerMapper answerMapper = answerMapperFactory.getAnswerMapper(question.getQuestionType());
return answerMapper.mapToAnswer(answerRequest);
}
diff --git a/backend/src/main/java/reviewme/review/service/mapper/TextAnswerMapper.java b/backend/src/main/java/reviewme/review/service/mapper/TextAnswerMapper.java
index afd47ac97..6f28faedd 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/TextAnswerMapper.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/TextAnswerMapper.java
@@ -1,10 +1,9 @@
package reviewme.review.service.mapper;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.QuestionType;
import reviewme.review.domain.TextAnswer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
-import reviewme.review.service.exception.TextAnswerIncludedOptionItemException;
+import reviewme.template.domain.QuestionType;
@Component
public class TextAnswerMapper implements AnswerMapper {
@@ -16,12 +15,9 @@ public boolean supports(QuestionType questionType) {
@Override
public TextAnswer mapToAnswer(ReviewAnswerRequest answerRequest) {
- if (!answerRequest.hasTextAnswer()) {
+ if (answerRequest.hasNoText()) {
return null;
}
- if (answerRequest.selectedOptionIds() != null) {
- throw new TextAnswerIncludedOptionItemException(answerRequest.questionId());
- }
return new TextAnswer(answerRequest.questionId(), answerRequest.text());
}
}
diff --git a/backend/src/main/java/reviewme/review/service/mapper/UnsupportedQuestionTypeException.java b/backend/src/main/java/reviewme/review/service/mapper/UnsupportedQuestionTypeException.java
index b08870515..26a22f0fd 100644
--- a/backend/src/main/java/reviewme/review/service/mapper/UnsupportedQuestionTypeException.java
+++ b/backend/src/main/java/reviewme/review/service/mapper/UnsupportedQuestionTypeException.java
@@ -2,7 +2,7 @@
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.DataInconsistencyException;
-import reviewme.question.domain.QuestionType;
+import reviewme.template.domain.QuestionType;
@Slf4j
public class UnsupportedQuestionTypeException extends DataInconsistencyException {
diff --git a/backend/src/main/java/reviewme/review/service/validator/AnswerValidator.java b/backend/src/main/java/reviewme/review/service/validator/AnswerValidator.java
index 11162cc26..bb9f85434 100644
--- a/backend/src/main/java/reviewme/review/service/validator/AnswerValidator.java
+++ b/backend/src/main/java/reviewme/review/service/validator/AnswerValidator.java
@@ -1,10 +1,31 @@
package reviewme.review.service.validator;
-import reviewme.review.domain.Answer;
+import java.util.Collection;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import reviewme.review.repository.AnswerRepository;
+import reviewme.review.service.exception.QuestionNotContainingAnswersException;
+import reviewme.review.service.exception.ReviewGroupNotContainingAnswersException;
+import reviewme.reviewgroup.domain.ReviewGroup;
-public interface AnswerValidator {
+@Component
+@RequiredArgsConstructor
+public class AnswerValidator {
- boolean supports(Class extends Answer> answerClass);
+ private final AnswerRepository answerRepository;
- void validate(Answer answer);
+ public void validateQuestionContainsAnswers(long questionId, Collection answerIds) {
+ Set receivedAnswerIds = answerRepository.findIdsByQuestionId(questionId);
+ if (!receivedAnswerIds.containsAll(answerIds)) {
+ throw new QuestionNotContainingAnswersException(questionId, answerIds);
+ }
+ }
+
+ public void validateReviewGroupContainsAnswers(ReviewGroup reviewGroup, Collection answerIds) {
+ Set receivedAnswerIds = answerRepository.findIdsByReviewGroupId(reviewGroup.getId());
+ if (!receivedAnswerIds.containsAll(answerIds)) {
+ throw new ReviewGroupNotContainingAnswersException(reviewGroup.getId(), answerIds);
+ }
+ }
}
diff --git a/backend/src/main/java/reviewme/review/service/validator/CheckboxAnswerValidator.java b/backend/src/main/java/reviewme/review/service/validator/CheckboxTypedAnswerValidator.java
similarity index 82%
rename from backend/src/main/java/reviewme/review/service/validator/CheckboxAnswerValidator.java
rename to backend/src/main/java/reviewme/review/service/validator/CheckboxTypedAnswerValidator.java
index 62d39728b..3ccead908 100644
--- a/backend/src/main/java/reviewme/review/service/validator/CheckboxAnswerValidator.java
+++ b/backend/src/main/java/reviewme/review/service/validator/CheckboxTypedAnswerValidator.java
@@ -5,12 +5,13 @@
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.SelectionRange;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+import reviewme.template.repository.OptionGroupRepository;
+import reviewme.template.repository.OptionItemRepository;
+import reviewme.template.repository.QuestionRepository;
import reviewme.review.domain.Answer;
import reviewme.review.domain.CheckboxAnswerSelectedOption;
import reviewme.review.domain.CheckboxAnswer;
@@ -21,7 +22,7 @@
@Component
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-public class CheckboxAnswerValidator implements AnswerValidator {
+public class CheckboxTypedAnswerValidator implements TypedAnswerValidator {
private final QuestionRepository questionRepository;
private final OptionGroupRepository optionGroupRepository;
@@ -60,15 +61,15 @@ private void validateOnlyIncludingProvidedOptionItem(CheckboxAnswer checkboxAnsw
}
private void validateCheckedOptionItemCount(CheckboxAnswer checkboxAnswer, OptionGroup optionGroup) {
+ SelectionRange selectionRange = optionGroup.getSelectionRange();
int answeredOptionItemCount = extractAnsweredOptionItemIds(checkboxAnswer).size();
- if (answeredOptionItemCount < optionGroup.getMinSelectionCount()
- || answeredOptionItemCount > optionGroup.getMaxSelectionCount()) {
+ if (selectionRange.isOutOfRange(answeredOptionItemCount)) {
throw new SelectedOptionItemCountOutOfRangeException(
checkboxAnswer.getQuestionId(),
answeredOptionItemCount,
- optionGroup.getMinSelectionCount(),
- optionGroup.getMaxSelectionCount()
+ selectionRange.getMinSelectionCount(),
+ selectionRange.getMaxSelectionCount()
);
}
}
diff --git a/backend/src/main/java/reviewme/review/service/validator/ReviewValidator.java b/backend/src/main/java/reviewme/review/service/validator/ReviewValidator.java
index 2906a8507..a480b3d9f 100644
--- a/backend/src/main/java/reviewme/review/service/validator/ReviewValidator.java
+++ b/backend/src/main/java/reviewme/review/service/validator/ReviewValidator.java
@@ -7,23 +7,22 @@
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Answer;
-import reviewme.review.domain.CheckboxAnswerSelectedOption;
import reviewme.review.domain.CheckboxAnswer;
+import reviewme.review.domain.CheckboxAnswerSelectedOption;
import reviewme.review.domain.Review;
import reviewme.review.service.exception.MissingRequiredQuestionException;
import reviewme.review.service.exception.SubmittedQuestionAndProvidedQuestionMismatchException;
+import reviewme.template.domain.Question;
import reviewme.template.domain.Section;
-import reviewme.template.domain.SectionQuestion;
+import reviewme.template.repository.QuestionRepository;
import reviewme.template.repository.SectionRepository;
@Component
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public class ReviewValidator {
- private final AnswerValidatorFactory answerValidatorFactory;
+ private final TypedAnswerValidatorFactory typedAnswerValidatorFactory;
private final SectionRepository sectionRepository;
private final QuestionRepository questionRepository;
@@ -36,8 +35,8 @@ public void validate(Review review) {
private void validateAnswer(List answers) {
for (Answer answer : answers) {
- AnswerValidator validator = answerValidatorFactory.getAnswerValidator(answer.getClass());
- validator.validate(answer);
+ typedAnswerValidatorFactory.getAnswerValidator(answer.getClass())
+ .validate(answer);
}
}
@@ -75,8 +74,8 @@ private Set extractDisplayedQuestionIds(Review review) {
return sections.stream()
.filter(section -> section.isVisibleBySelectedOptionIds(selectedOptionIds))
- .flatMap(section -> section.getQuestionIds().stream())
- .map(SectionQuestion::getQuestionId)
+ .flatMap(section -> section.getQuestions().stream())
+ .map(Question::getId)
.collect(Collectors.toSet());
}
}
diff --git a/backend/src/main/java/reviewme/review/service/validator/TextAnswerValidator.java b/backend/src/main/java/reviewme/review/service/validator/TextTypedAnswerValidator.java
similarity index 88%
rename from backend/src/main/java/reviewme/review/service/validator/TextAnswerValidator.java
rename to backend/src/main/java/reviewme/review/service/validator/TextTypedAnswerValidator.java
index 78a0701dd..0dffa56c1 100644
--- a/backend/src/main/java/reviewme/review/service/validator/TextAnswerValidator.java
+++ b/backend/src/main/java/reviewme/review/service/validator/TextTypedAnswerValidator.java
@@ -3,8 +3,8 @@
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
+import reviewme.template.domain.Question;
+import reviewme.template.repository.QuestionRepository;
import reviewme.review.domain.Answer;
import reviewme.review.domain.TextAnswer;
import reviewme.review.service.exception.InvalidTextAnswerLengthException;
@@ -12,9 +12,8 @@
@Component
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-public class TextAnswerValidator implements AnswerValidator {
+public class TextTypedAnswerValidator implements TypedAnswerValidator {
- private static final int ZERO_LENGTH = 0;
private static final int MIN_LENGTH = 20;
private static final int MAX_LENGTH = 1_000;
diff --git a/backend/src/main/java/reviewme/review/service/validator/TypedAnswerValidator.java b/backend/src/main/java/reviewme/review/service/validator/TypedAnswerValidator.java
new file mode 100644
index 000000000..2bc060c52
--- /dev/null
+++ b/backend/src/main/java/reviewme/review/service/validator/TypedAnswerValidator.java
@@ -0,0 +1,10 @@
+package reviewme.review.service.validator;
+
+import reviewme.review.domain.Answer;
+
+public interface TypedAnswerValidator {
+
+ boolean supports(Class extends Answer> answerClass);
+
+ void validate(Answer answer);
+}
diff --git a/backend/src/main/java/reviewme/review/service/validator/AnswerValidatorFactory.java b/backend/src/main/java/reviewme/review/service/validator/TypedAnswerValidatorFactory.java
similarity index 68%
rename from backend/src/main/java/reviewme/review/service/validator/AnswerValidatorFactory.java
rename to backend/src/main/java/reviewme/review/service/validator/TypedAnswerValidatorFactory.java
index b1adc5933..a0ff54733 100644
--- a/backend/src/main/java/reviewme/review/service/validator/AnswerValidatorFactory.java
+++ b/backend/src/main/java/reviewme/review/service/validator/TypedAnswerValidatorFactory.java
@@ -8,12 +8,12 @@
@Component
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
-public class AnswerValidatorFactory {
+public class TypedAnswerValidatorFactory {
- private final List answerValidators;
+ private final List validators;
- public AnswerValidator getAnswerValidator(Class extends Answer> answerClass) {
- return answerValidators.stream()
+ public TypedAnswerValidator getAnswerValidator(Class extends Answer> answerClass) {
+ return validators.stream()
.filter(validator -> validator.supports(answerClass))
.findFirst()
.orElseThrow(() -> new UnsupportedAnswerTypeException(answerClass));
diff --git a/backend/src/main/java/reviewme/reviewgroup/controller/ReviewGroupController.java b/backend/src/main/java/reviewme/reviewgroup/controller/ReviewGroupController.java
index b6c7a973c..fa82ba70d 100644
--- a/backend/src/main/java/reviewme/reviewgroup/controller/ReviewGroupController.java
+++ b/backend/src/main/java/reviewme/reviewgroup/controller/ReviewGroupController.java
@@ -15,6 +15,7 @@
import reviewme.reviewgroup.service.dto.CheckValidAccessRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationResponse;
+import reviewme.reviewgroup.service.dto.ReviewGroupPageResponse;
import reviewme.reviewgroup.service.dto.ReviewGroupResponse;
@RestController
@@ -24,7 +25,7 @@ public class ReviewGroupController {
private final ReviewGroupService reviewGroupService;
private final ReviewGroupLookupService reviewGroupLookupService;
- @GetMapping("/v2/groups")
+ @GetMapping("/v2/groups/summary")
public ResponseEntity getReviewGroupSummary(@RequestParam String reviewRequestCode) {
ReviewGroupResponse response = reviewGroupLookupService.getReviewGroupSummary(reviewRequestCode);
return ResponseEntity.ok(response);
@@ -34,6 +35,7 @@ public ResponseEntity getReviewGroupSummary(@RequestParam S
public ResponseEntity createReviewGroup(
@Valid @RequestBody ReviewGroupCreationRequest request
) {
+ // 회원 세션 추후 추가해야 함
ReviewGroupCreationResponse response = reviewGroupService.createReviewGroup(request);
return ResponseEntity.ok(response);
}
@@ -48,4 +50,11 @@ public ResponseEntity checkGroupAccessCode(
session.setAttribute("reviewRequestCode", request.reviewRequestCode());
return ResponseEntity.noContent().build();
}
+
+ @GetMapping("/v2/groups")
+ public ResponseEntity getMyReviewGroups() {
+ // TODO: 세션을 활용한 권한 체계에 따른 추가 조치 필요
+ ReviewGroupPageResponse response = reviewGroupLookupService.getMyReviewGroups();
+ return ResponseEntity.ok(response);
+ }
}
diff --git a/backend/src/main/java/reviewme/reviewgroup/domain/ReviewGroup.java b/backend/src/main/java/reviewme/reviewgroup/domain/ReviewGroup.java
index dcc97fefe..6e95967bd 100644
--- a/backend/src/main/java/reviewme/reviewgroup/domain/ReviewGroup.java
+++ b/backend/src/main/java/reviewme/reviewgroup/domain/ReviewGroup.java
@@ -11,8 +11,8 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import reviewme.review.domain.exception.InvalidProjectNameLengthException;
-import reviewme.review.domain.exception.InvalidRevieweeNameLengthException;
+import reviewme.reviewgroup.domain.exception.InvalidProjectNameLengthException;
+import reviewme.reviewgroup.domain.exception.InvalidRevieweeNameLengthException;
@Entity
@Table(name = "review_group")
diff --git a/backend/src/main/java/reviewme/review/domain/exception/InvalidProjectNameLengthException.java b/backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidProjectNameLengthException.java
similarity index 92%
rename from backend/src/main/java/reviewme/review/domain/exception/InvalidProjectNameLengthException.java
rename to backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidProjectNameLengthException.java
index 2e6386bb4..75b56ba43 100644
--- a/backend/src/main/java/reviewme/review/domain/exception/InvalidProjectNameLengthException.java
+++ b/backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidProjectNameLengthException.java
@@ -1,4 +1,4 @@
-package reviewme.review.domain.exception;
+package reviewme.reviewgroup.domain.exception;
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;
diff --git a/backend/src/main/java/reviewme/review/domain/exception/InvalidRevieweeNameLengthException.java b/backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidRevieweeNameLengthException.java
similarity index 92%
rename from backend/src/main/java/reviewme/review/domain/exception/InvalidRevieweeNameLengthException.java
rename to backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidRevieweeNameLengthException.java
index 27408d15b..e77562a23 100644
--- a/backend/src/main/java/reviewme/review/domain/exception/InvalidRevieweeNameLengthException.java
+++ b/backend/src/main/java/reviewme/reviewgroup/domain/exception/InvalidRevieweeNameLengthException.java
@@ -1,4 +1,4 @@
-package reviewme.review.domain.exception;
+package reviewme.reviewgroup.domain.exception;
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.BadRequestException;
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupLookupService.java b/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupLookupService.java
index 38bd711af..479b59116 100644
--- a/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupLookupService.java
+++ b/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupLookupService.java
@@ -3,7 +3,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.reviewgroup.service.dto.ReviewGroupPageResponse;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.reviewgroup.service.dto.ReviewGroupResponse;
@@ -19,6 +20,11 @@ public ReviewGroupResponse getReviewGroupSummary(String reviewRequestCode) {
ReviewGroup reviewGroup = reviewGroupRepository.findByReviewRequestCode(reviewRequestCode)
.orElseThrow(() -> new ReviewGroupNotFoundByReviewRequestCodeException(reviewRequestCode));
- return new ReviewGroupResponse(reviewGroup.getReviewee(), reviewGroup.getProjectName());
+ return new ReviewGroupResponse(null, reviewGroup.getReviewee(), reviewGroup.getProjectName());
+ }
+
+ public ReviewGroupPageResponse getMyReviewGroups() {
+ // TODO: 생성일자 최신순 정렬
+ return null;
}
}
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupService.java b/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupService.java
index 1ae76f6a0..fb09f6cb0 100644
--- a/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupService.java
+++ b/backend/src/main/java/reviewme/reviewgroup/service/ReviewGroupService.java
@@ -3,13 +3,13 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
-import reviewme.review.service.exception.ReviewGroupUnauthorizedException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.reviewgroup.service.dto.CheckValidAccessRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationResponse;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.reviewgroup.service.exception.ReviewGroupUnauthorizedException;
import reviewme.template.domain.Template;
import reviewme.template.repository.TemplateRepository;
import reviewme.template.service.exception.TemplateNotFoundException;
@@ -27,6 +27,7 @@ public class ReviewGroupService {
@Transactional
public ReviewGroupCreationResponse createReviewGroup(ReviewGroupCreationRequest request) {
+ // 회원, 비회원 분기 처리 필요
String reviewRequestCode;
do {
reviewRequestCode = randomCodeGenerator.generate(REVIEW_REQUEST_CODE_LENGTH);
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/dto/CheckValidAccessResponse.java b/backend/src/main/java/reviewme/reviewgroup/service/dto/CheckValidAccessResponse.java
deleted file mode 100644
index 01444a880..000000000
--- a/backend/src/main/java/reviewme/reviewgroup/service/dto/CheckValidAccessResponse.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package reviewme.reviewgroup.service.dto;
-
-public record CheckValidAccessResponse(
- boolean hasAccess
-) {
-}
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/dto/MemberReviewGroupCreationRequest.java b/backend/src/main/java/reviewme/reviewgroup/service/dto/MemberReviewGroupCreationRequest.java
new file mode 100644
index 000000000..c76c3c19c
--- /dev/null
+++ b/backend/src/main/java/reviewme/reviewgroup/service/dto/MemberReviewGroupCreationRequest.java
@@ -0,0 +1,13 @@
+package reviewme.reviewgroup.service.dto;
+
+import jakarta.validation.constraints.NotEmpty;
+
+public record MemberReviewGroupCreationRequest(
+
+ @NotEmpty(message = "리뷰이 이름을 입력해주세요.")
+ String revieweeName,
+
+ @NotEmpty(message = "프로젝트 이름을 입력해주세요.")
+ String projectName
+) {
+}
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupCreationRequest.java b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupCreationRequest.java
index c31a70f04..d8c1a0a0b 100644
--- a/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupCreationRequest.java
+++ b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupCreationRequest.java
@@ -1,6 +1,6 @@
package reviewme.reviewgroup.service.dto;
-import jakarta.validation.constraints.NotBlank;
+import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotEmpty;
public record ReviewGroupCreationRequest(
@@ -11,7 +11,7 @@ public record ReviewGroupCreationRequest(
@NotEmpty(message = "프로젝트 이름을 입력해주세요.")
String projectName,
- @NotBlank(message = "비밀번호를 입력해주세요.")
+ @Nullable
String groupAccessCode
) {
}
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupPageElementResponse.java b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupPageElementResponse.java
new file mode 100644
index 000000000..f5a7aab78
--- /dev/null
+++ b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupPageElementResponse.java
@@ -0,0 +1,12 @@
+package reviewme.reviewgroup.service.dto;
+
+import java.time.LocalDate;
+
+public record ReviewGroupPageElementResponse(
+ String revieweeName,
+ String projectName,
+ LocalDate createdAt,
+ String reviewRequestCode,
+ int reviewCount
+) {
+}
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupPageResponse.java b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupPageResponse.java
new file mode 100644
index 000000000..ef6c250e9
--- /dev/null
+++ b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupPageResponse.java
@@ -0,0 +1,10 @@
+package reviewme.reviewgroup.service.dto;
+
+import java.util.List;
+
+public record ReviewGroupPageResponse(
+ long lastReviewGroupId,
+ boolean isLastPage,
+ List reviewGroups
+) {
+}
diff --git a/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupResponse.java b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupResponse.java
index ea6f12a29..4d1accfbf 100644
--- a/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupResponse.java
+++ b/backend/src/main/java/reviewme/reviewgroup/service/dto/ReviewGroupResponse.java
@@ -1,7 +1,10 @@
package reviewme.reviewgroup.service.dto;
+import jakarta.annotation.Nullable;
+
public record ReviewGroupResponse(
+ @Nullable Long revieweeId,
String revieweeName,
String projectName
) {
diff --git a/backend/src/main/java/reviewme/review/service/exception/ReviewGroupNotFoundByReviewRequestCodeException.java b/backend/src/main/java/reviewme/reviewgroup/service/exception/ReviewGroupNotFoundByReviewRequestCodeException.java
similarity index 90%
rename from backend/src/main/java/reviewme/review/service/exception/ReviewGroupNotFoundByReviewRequestCodeException.java
rename to backend/src/main/java/reviewme/reviewgroup/service/exception/ReviewGroupNotFoundByReviewRequestCodeException.java
index 121296482..5761e3c8d 100644
--- a/backend/src/main/java/reviewme/review/service/exception/ReviewGroupNotFoundByReviewRequestCodeException.java
+++ b/backend/src/main/java/reviewme/reviewgroup/service/exception/ReviewGroupNotFoundByReviewRequestCodeException.java
@@ -1,4 +1,4 @@
-package reviewme.review.service.exception;
+package reviewme.reviewgroup.service.exception;
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.NotFoundException;
diff --git a/backend/src/main/java/reviewme/review/service/exception/ReviewGroupUnauthorizedException.java b/backend/src/main/java/reviewme/reviewgroup/service/exception/ReviewGroupUnauthorizedException.java
similarity index 92%
rename from backend/src/main/java/reviewme/review/service/exception/ReviewGroupUnauthorizedException.java
rename to backend/src/main/java/reviewme/reviewgroup/service/exception/ReviewGroupUnauthorizedException.java
index 125f2e7e9..64c106e84 100644
--- a/backend/src/main/java/reviewme/review/service/exception/ReviewGroupUnauthorizedException.java
+++ b/backend/src/main/java/reviewme/reviewgroup/service/exception/ReviewGroupUnauthorizedException.java
@@ -1,4 +1,4 @@
-package reviewme.review.service.exception;
+package reviewme.reviewgroup.service.exception;
import lombok.extern.slf4j.Slf4j;
import reviewme.global.exception.UnauthorizedException;
diff --git a/backend/src/main/java/reviewme/template/controller/SectionController.java b/backend/src/main/java/reviewme/template/controller/SectionController.java
index 23826d87f..cf1e1b467 100644
--- a/backend/src/main/java/reviewme/template/controller/SectionController.java
+++ b/backend/src/main/java/reviewme/template/controller/SectionController.java
@@ -6,20 +6,20 @@
import org.springframework.web.bind.annotation.RestController;
import reviewme.reviewgroup.controller.ReviewGroupSession;
import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.template.service.SectionService;
+import reviewme.template.service.TemplateService;
import reviewme.template.service.dto.response.SectionNamesResponse;
@RestController
@RequiredArgsConstructor
public class SectionController {
- private final SectionService sectionService;
+ private final TemplateService templateService;
@GetMapping("/v2/sections")
public ResponseEntity getSectionNames(
@ReviewGroupSession ReviewGroup reviewGroup
) {
- SectionNamesResponse sectionNames = sectionService.getSectionNames(reviewGroup);
+ SectionNamesResponse sectionNames = templateService.getSectionNames(reviewGroup);
return ResponseEntity.ok(sectionNames);
}
}
diff --git a/backend/src/main/java/reviewme/template/domain/EmptyOptionGroupException.java b/backend/src/main/java/reviewme/template/domain/EmptyOptionGroupException.java
new file mode 100644
index 000000000..c34315d82
--- /dev/null
+++ b/backend/src/main/java/reviewme/template/domain/EmptyOptionGroupException.java
@@ -0,0 +1,13 @@
+package reviewme.template.domain;
+
+import lombok.extern.slf4j.Slf4j;
+import reviewme.global.exception.BadRequestException;
+
+@Slf4j
+public class EmptyOptionGroupException extends BadRequestException {
+
+ public EmptyOptionGroupException() {
+ super("옵션 아이템은 최소 한 개 이상이어야 해요.");
+ log.info("OptionItems were empty while creating Option Group.");
+ }
+}
diff --git a/backend/src/main/java/reviewme/template/domain/InvalidSelectionRangeException.java b/backend/src/main/java/reviewme/template/domain/InvalidSelectionRangeException.java
new file mode 100644
index 000000000..1d0b0fa8d
--- /dev/null
+++ b/backend/src/main/java/reviewme/template/domain/InvalidSelectionRangeException.java
@@ -0,0 +1,20 @@
+package reviewme.template.domain;
+
+import lombok.extern.slf4j.Slf4j;
+import reviewme.global.exception.BadRequestException;
+
+@Slf4j
+public class InvalidSelectionRangeException extends BadRequestException {
+
+ public InvalidSelectionRangeException(int size, int minSelectionCount, int maxSelectionCount) {
+ super("선택 가능 범위가 잘못 설정되었어요.");
+ log.info("Invalid selection range on OptionGroup: OptionGroup size={}, minSelectionCount={}, maxSelectionCount={}",
+ size, minSelectionCount, maxSelectionCount);
+ }
+
+ public InvalidSelectionRangeException(int minSelectionCount, int maxSelectionCount) {
+ super("선택 가능 범위가 잘못 설정되었어요.");
+ log.info("Invalid selection range: minSelectionCount={}, maxSelectionCount={}",
+ minSelectionCount, maxSelectionCount);
+ }
+}
diff --git a/backend/src/main/java/reviewme/template/domain/OptionGroup.java b/backend/src/main/java/reviewme/template/domain/OptionGroup.java
new file mode 100644
index 000000000..a71db79c7
--- /dev/null
+++ b/backend/src/main/java/reviewme/template/domain/OptionGroup.java
@@ -0,0 +1,46 @@
+package reviewme.template.domain;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Embedded;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Entity
+@Table(name = "option_group")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode(of = "id")
+@Getter
+public class OptionGroup {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
+ @JoinColumn(name = "option_group_id", nullable = false, updatable = false)
+ private List optionItems;
+
+ @Embedded
+ private SelectionRange selectionRange;
+
+ public OptionGroup(List optionItems, int minSelectionCount, int maxSelectionCount) {
+ if (optionItems.isEmpty()) {
+ throw new EmptyOptionGroupException();
+ }
+ this.optionItems = optionItems;
+ this.selectionRange = new SelectionRange(minSelectionCount, maxSelectionCount);
+ if (optionItems.size() < maxSelectionCount) {
+ throw new InvalidSelectionRangeException(optionItems.size(), minSelectionCount, maxSelectionCount);
+ }
+ }
+}
diff --git a/backend/src/main/java/reviewme/question/domain/OptionItem.java b/backend/src/main/java/reviewme/template/domain/OptionItem.java
similarity index 79%
rename from backend/src/main/java/reviewme/question/domain/OptionItem.java
rename to backend/src/main/java/reviewme/template/domain/OptionItem.java
index 59b29bc3b..b76782a67 100644
--- a/backend/src/main/java/reviewme/question/domain/OptionItem.java
+++ b/backend/src/main/java/reviewme/template/domain/OptionItem.java
@@ -1,4 +1,4 @@
-package reviewme.question.domain;
+package reviewme.template.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
@@ -27,19 +27,16 @@ public class OptionItem {
@Column(name = "content", nullable = false)
private String content;
- @Column(name = "option_group_id", nullable = false)
- private long optionGroupId;
-
@Column(name = "position", nullable = false)
private int position;
+ // TODO: 카테고리/키워드 여부는 도메인 로직이 아니라 서비스단에 있는 것이 자연스럽다고 생각함
@Column(name = "option_type", nullable = false)
@Enumerated(EnumType.STRING)
private OptionType optionType;
- public OptionItem(String content, long optionGroupId, int position, OptionType optionType) {
+ public OptionItem(String content, int position, OptionType optionType) {
this.content = content;
- this.optionGroupId = optionGroupId;
this.position = position;
this.optionType = optionType;
}
diff --git a/backend/src/main/java/reviewme/question/domain/OptionType.java b/backend/src/main/java/reviewme/template/domain/OptionType.java
similarity index 57%
rename from backend/src/main/java/reviewme/question/domain/OptionType.java
rename to backend/src/main/java/reviewme/template/domain/OptionType.java
index dfa86920b..cf20adc26 100644
--- a/backend/src/main/java/reviewme/question/domain/OptionType.java
+++ b/backend/src/main/java/reviewme/template/domain/OptionType.java
@@ -1,6 +1,8 @@
-package reviewme.question.domain;
+package reviewme.template.domain;
public enum OptionType {
+
CATEGORY,
KEYWORD,
+ ;
}
diff --git a/backend/src/main/java/reviewme/question/domain/Question.java b/backend/src/main/java/reviewme/template/domain/Question.java
similarity index 57%
rename from backend/src/main/java/reviewme/question/domain/Question.java
rename to backend/src/main/java/reviewme/template/domain/Question.java
index f59e4ae87..6ca22f979 100644
--- a/backend/src/main/java/reviewme/question/domain/Question.java
+++ b/backend/src/main/java/reviewme/template/domain/Question.java
@@ -1,5 +1,6 @@
-package reviewme.question.domain;
+package reviewme.template.domain;
+import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
@@ -7,6 +8,7 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
+import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
@@ -31,6 +33,9 @@ public class Question {
@Enumerated(EnumType.STRING)
private QuestionType questionType;
+ @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
+ private OptionGroup optionGroup;
+
@Column(name = "content", nullable = false, length = 1_000)
private String content;
@@ -40,15 +45,26 @@ public class Question {
@Column(name = "position", nullable = false)
private int position;
- public Question(boolean required, QuestionType questionType, String content, String guideline, int position) {
+ // 질문 타입에 따른 Factory가 필요할 수 있다. Checkbox인 경우 OptionGroup을 가지게 하고, Text인 경우 그렇지 않고...
+ // Required도 마찬가지로 Factory에서 설정해준다면 content, guideline, position과 같은 필수적인 정보만 생성자에 넣어주면 된다.
+ // 사실 Position도 List의 순서에 따라 자동으로 배정하면 좋겠다. 같은 Section 안에 같은 position을 가질 수 없다는 불변식이 깨질 위험이 존재한다.
+ // TODO: @OrderColumn을 사용해 Position 사용하지 않고 자동 설정
+ // TODO: QuestionType에 따른 검증 로직 추가
+ public Question(boolean required, QuestionType questionType, OptionGroup optionGroup,
+ String content, String guideline, int position) {
this.required = required;
this.questionType = questionType;
+ this.optionGroup = optionGroup;
this.content = content;
this.guideline = guideline;
this.position = position;
}
- public boolean isSelectable() {
+ public Question(boolean required, QuestionType questionType, String content, String guideline, int position) {
+ this(required, questionType, null, content, guideline, position);
+ }
+
+ public boolean isCheckbox() {
return questionType == QuestionType.CHECKBOX;
}
diff --git a/backend/src/main/java/reviewme/question/domain/QuestionType.java b/backend/src/main/java/reviewme/template/domain/QuestionType.java
similarity index 64%
rename from backend/src/main/java/reviewme/question/domain/QuestionType.java
rename to backend/src/main/java/reviewme/template/domain/QuestionType.java
index 863ba56e5..4213ce925 100644
--- a/backend/src/main/java/reviewme/question/domain/QuestionType.java
+++ b/backend/src/main/java/reviewme/template/domain/QuestionType.java
@@ -1,8 +1,8 @@
-package reviewme.question.domain;
+package reviewme.template.domain;
public enum QuestionType {
+
CHECKBOX,
TEXT,
;
-
}
diff --git a/backend/src/main/java/reviewme/template/domain/Section.java b/backend/src/main/java/reviewme/template/domain/Section.java
index b9fa82b16..198f7812c 100644
--- a/backend/src/main/java/reviewme/template/domain/Section.java
+++ b/backend/src/main/java/reviewme/template/domain/Section.java
@@ -10,6 +10,7 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.Collection;
@@ -36,10 +37,11 @@ public class Section {
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "section_id", nullable = false, updatable = false)
- private List questionIds;
+ private List questions;
- @Column(name = "on_selected_option_id", nullable = true)
- private Long onSelectedOptionId;
+ @ManyToOne(fetch = FetchType.LAZY, optional = true)
+ @JoinColumn(name = "on_selected_option_id", nullable = true)
+ private OptionItem onSelectedOption;
@Column(name = "section_name", nullable = false)
private String sectionName;
@@ -50,24 +52,31 @@ public class Section {
@Column(name = "position", nullable = false)
private int position;
- public Section(VisibleType visibleType, List questionIds,
- Long onSelectedOptionId, String sectionName, String header, int position) {
+ public Section(VisibleType visibleType, List questions,
+ OptionItem onSelectedOption, String sectionName, String header, int position) {
+ if (questions.isEmpty()) {
+ throw new IllegalArgumentException("질문은 최소 한 개 이상이어야 합니다.");
+ }
+ if (visibleType == VisibleType.CONDITIONAL && onSelectedOption == null) {
+ throw new IllegalArgumentException("조건부 표시인 경우 선택 옵션이 필수입니다.");
+ }
this.visibleType = visibleType;
- this.questionIds = questionIds.stream()
- .map(SectionQuestion::new)
- .toList();
- this.onSelectedOptionId = onSelectedOptionId;
+ this.questions = questions;
+ this.onSelectedOption = onSelectedOption;
this.sectionName = sectionName;
this.header = header;
this.position = position;
}
public boolean isVisibleBySelectedOptionIds(Collection selectedOptionIds) {
- return visibleType == VisibleType.ALWAYS || selectedOptionIds.contains(onSelectedOptionId);
+ return visibleType == VisibleType.ALWAYS || selectedOptionIds.contains(onSelectedOption.getId());
}
- public boolean containsQuestionId(long questionId) {
- return questionIds.stream()
- .anyMatch(sectionQuestion -> sectionQuestion.hasQuestionId(questionId));
+ public boolean contains(Question question) {
+ return questions.contains(question);
+ }
+
+ public boolean isConditional() {
+ return visibleType == VisibleType.CONDITIONAL;
}
}
diff --git a/backend/src/main/java/reviewme/template/domain/SectionQuestion.java b/backend/src/main/java/reviewme/template/domain/SectionQuestion.java
deleted file mode 100644
index 36bed180c..000000000
--- a/backend/src/main/java/reviewme/template/domain/SectionQuestion.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package reviewme.template.domain;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-
-@Entity
-@Table(name = "section_question")
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-@Getter
-public class SectionQuestion {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(name = "section_id", nullable = false, insertable = false, updatable = false)
- private long sectionId;
-
- @Column(name = "question_id", nullable = false)
- private long questionId;
-
- public SectionQuestion(long questionId) {
- this.questionId = questionId;
- }
-
- public boolean hasQuestionId(long questionId) {
- return this.questionId == questionId;
- }
-}
diff --git a/backend/src/main/java/reviewme/template/domain/SelectionRange.java b/backend/src/main/java/reviewme/template/domain/SelectionRange.java
new file mode 100644
index 000000000..a3d65c321
--- /dev/null
+++ b/backend/src/main/java/reviewme/template/domain/SelectionRange.java
@@ -0,0 +1,33 @@
+package reviewme.template.domain;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Embeddable
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@Getter
+@EqualsAndHashCode
+public class SelectionRange {
+
+ @Column(name = "min_selection_count", nullable = false)
+ private int minSelectionCount;
+
+ @Column(name = "max_selection_count", nullable = false)
+ private int maxSelectionCount;
+
+ public SelectionRange(int minSelectionCount, int maxSelectionCount) {
+ if (minSelectionCount < 0 || minSelectionCount > maxSelectionCount) {
+ throw new InvalidSelectionRangeException(minSelectionCount, maxSelectionCount);
+ }
+ this.minSelectionCount = minSelectionCount;
+ this.maxSelectionCount = maxSelectionCount;
+ }
+
+ public boolean isOutOfRange(int selectionCount) {
+ return selectionCount < minSelectionCount || selectionCount > maxSelectionCount;
+ }
+}
diff --git a/backend/src/main/java/reviewme/template/domain/Template.java b/backend/src/main/java/reviewme/template/domain/Template.java
index 29b6f36d7..c576d3fab 100644
--- a/backend/src/main/java/reviewme/template/domain/Template.java
+++ b/backend/src/main/java/reviewme/template/domain/Template.java
@@ -15,6 +15,8 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
+// Aggregate root
+// 템플릿 하나를 만들고 이를 수정/삭제할 수 있다. 이는 하나의 애그리거트에서 일어나는 것이 자연스럽다.
@Entity
@Table(name = "template")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@@ -26,13 +28,17 @@ public class Template {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
- @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
+ // Template : Section은 1 : N 관계이다. Section을 여러 곳에서 재사용한다고 생각하니 머리가 아프다. 정말 재사용할 일이 있을까? 싶다.
+ // 마찬가지로 Section : Question도 1 : N 관계이다. Question 또한 재사용될 일이 있을까? N:M이 아닌 1:N 관계로 생각해보자
+ @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "template_id", nullable = false, updatable = false)
- private List sectionIds;
+ private List sections;
- public Template(List sectionIds) {
- this.sectionIds = sectionIds.stream()
- .map(TemplateSection::new)
- .toList();
+ public Template(List sections) {
+ if (sections.isEmpty()) {
+ throw new IllegalArgumentException("섹션은 최소 한 개 이상이어야 합니다.");
+ }
+ // TODO: Max section count limit?
+ this.sections = sections;
}
}
diff --git a/backend/src/main/java/reviewme/template/domain/TemplateSection.java b/backend/src/main/java/reviewme/template/domain/TemplateSection.java
deleted file mode 100644
index 6d451ee80..000000000
--- a/backend/src/main/java/reviewme/template/domain/TemplateSection.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package reviewme.template.domain;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
-import lombok.AccessLevel;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-
-@Entity
-@Table(name = "template_section")
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
-@Getter
-public class TemplateSection {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(name = "template_id", nullable = false, insertable = false, updatable = false)
- private long templateId;
-
- @Column(name = "section_id", nullable = false)
- private long sectionId;
-
- public TemplateSection(long sectionId) {
- this.sectionId = sectionId;
- }
-}
diff --git a/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java b/backend/src/main/java/reviewme/template/repository/OptionGroupRepository.java
similarity index 59%
rename from backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java
rename to backend/src/main/java/reviewme/template/repository/OptionGroupRepository.java
index ad2994537..c7440e6ff 100644
--- a/backend/src/main/java/reviewme/question/repository/OptionGroupRepository.java
+++ b/backend/src/main/java/reviewme/template/repository/OptionGroupRepository.java
@@ -1,20 +1,18 @@
-package reviewme.question.repository;
+package reviewme.template.repository;
-import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
-import reviewme.question.domain.OptionGroup;
+import reviewme.template.domain.OptionGroup;
@Repository
public interface OptionGroupRepository extends JpaRepository {
- Optional findByQuestionId(long questionId);
-
@Query("""
- SELECT og FROM OptionGroup og
- WHERE og.questionId IN :questionIds
+ SELECT og FROM Question q
+ JOIN q.optionGroup og
+ WHERE q.id = :questionId
""")
- List findAllByQuestionIds(List questionIds);
+ Optional findByQuestionId(long questionId);
}
diff --git a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java b/backend/src/main/java/reviewme/template/repository/OptionItemRepository.java
similarity index 61%
rename from backend/src/main/java/reviewme/question/repository/OptionItemRepository.java
rename to backend/src/main/java/reviewme/template/repository/OptionItemRepository.java
index e42274c33..ae96cb91b 100644
--- a/backend/src/main/java/reviewme/question/repository/OptionItemRepository.java
+++ b/backend/src/main/java/reviewme/template/repository/OptionItemRepository.java
@@ -1,15 +1,20 @@
-package reviewme.question.repository;
+package reviewme.template.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.OptionType;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.OptionType;
@Repository
public interface OptionItemRepository extends JpaRepository {
+ @Query("""
+ SELECT o FROM OptionGroup og
+ JOIN og.optionItems o
+ WHERE og.id = :optionGroupId
+ """)
List findAllByOptionGroupId(long optionGroupId);
@Query("""
@@ -17,12 +22,4 @@ public interface OptionItemRepository extends JpaRepository {
WHERE o.optionType = :optionType
""")
List findAllByOptionType(OptionType optionType);
-
- @Query("""
- SELECT o FROM OptionItem o
- JOIN OptionGroup og
- ON o.optionGroupId = og.id
- WHERE og.questionId IN :questionIds
- """)
- List findAllByQuestionIds(List questionIds);
}
diff --git a/backend/src/main/java/reviewme/template/repository/QuestionRepository.java b/backend/src/main/java/reviewme/template/repository/QuestionRepository.java
new file mode 100644
index 000000000..1722f5ee5
--- /dev/null
+++ b/backend/src/main/java/reviewme/template/repository/QuestionRepository.java
@@ -0,0 +1,38 @@
+package reviewme.template.repository;
+
+import java.util.List;
+import java.util.Set;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+
+@Repository
+public interface QuestionRepository extends JpaRepository {
+
+ @Query("""
+ SELECT q.id FROM Template t
+ JOIN t.sections s
+ JOIN s.questions q
+ WHERE t.id = :templateId
+ """)
+ Set findAllQuestionIdByTemplateId(long templateId);
+
+ @Query("""
+ SELECT q FROM Section s
+ Join s.questions q
+ WHERE s.id = :sectionId
+ ORDER BY q.position
+ """)
+ List findAllBySectionIdOrderByPosition(long sectionId);
+
+ @Query("""
+ SELECT o FROM Question q
+ JOIN q.optionGroup og
+ JOIN og.optionItems o
+ WHERE q.id = :questionId
+ ORDER BY o.position
+ """)
+ List findAllOptionItemsByIdOrderByPosition(long questionId);
+}
diff --git a/backend/src/main/java/reviewme/template/repository/SectionRepository.java b/backend/src/main/java/reviewme/template/repository/SectionRepository.java
index d40fa5a24..497a8247f 100644
--- a/backend/src/main/java/reviewme/template/repository/SectionRepository.java
+++ b/backend/src/main/java/reviewme/template/repository/SectionRepository.java
@@ -11,19 +11,18 @@
public interface SectionRepository extends JpaRepository {
@Query("""
- SELECT s FROM Section s
- JOIN TemplateSection ts
- ON s.id = ts.sectionId
- WHERE ts.templateId = :templateId
+ SELECT s FROM Template t
+ JOIN t.sections s
+ WHERE t.id = :templateId
ORDER BY s.position ASC
""")
List findAllByTemplateId(long templateId);
@Query("""
- SELECT s FROM Section s
- JOIN TemplateSection ts ON s.id = ts.sectionId
- WHERE ts.sectionId = :sectionId
- AND ts.templateId = :templateId
+ SELECT s FROM Template t
+ JOIN t.sections s
+ WHERE s.id = :sectionId
+ AND t.id = :templateId
""")
Optional findByIdAndTemplateId(long sectionId, long templateId);
}
diff --git a/backend/src/main/java/reviewme/template/service/SectionService.java b/backend/src/main/java/reviewme/template/service/SectionService.java
deleted file mode 100644
index e52347042..000000000
--- a/backend/src/main/java/reviewme/template/service/SectionService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package reviewme.template.service;
-
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.service.dto.response.SectionNameResponse;
-import reviewme.template.service.dto.response.SectionNamesResponse;
-
-@Service
-@RequiredArgsConstructor
-public class SectionService {
-
- private final SectionRepository sectionRepository;
-
- @Transactional(readOnly = true)
- public SectionNamesResponse getSectionNames(ReviewGroup reviewGroup) {
- List sectionNameResponses = sectionRepository.findAllByTemplateId(
- reviewGroup.getTemplateId())
- .stream()
- .map(section -> new SectionNameResponse(section.getId(), section.getSectionName()))
- .toList();
-
- return new SectionNamesResponse(sectionNameResponses);
- }
-}
diff --git a/backend/src/main/java/reviewme/template/service/TemplateService.java b/backend/src/main/java/reviewme/template/service/TemplateService.java
index a49fc5160..bf20bb030 100644
--- a/backend/src/main/java/reviewme/template/service/TemplateService.java
+++ b/backend/src/main/java/reviewme/template/service/TemplateService.java
@@ -3,27 +3,37 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.reviewgroup.repository.ReviewGroupRepository;
+import reviewme.reviewgroup.service.ReviewGroupService;
+import reviewme.template.domain.Template;
+import reviewme.template.repository.TemplateRepository;
+import reviewme.template.service.dto.response.SectionNamesResponse;
import reviewme.template.service.dto.response.TemplateResponse;
-import reviewme.template.service.mapper.TemplateMapper;
+import reviewme.template.service.exception.TemplateNotFoundByReviewGroupException;
@Service
@RequiredArgsConstructor
public class TemplateService {
- private final ReviewGroupRepository reviewGroupRepository;
- private final TemplateMapper templateMapper;
+ private final ReviewGroupService reviewGroupService;
+ private final TemplateRepository templateRepository;
@Transactional(readOnly = true)
public TemplateResponse generateReviewForm(String reviewRequestCode) {
- ReviewGroup reviewGroup = findReviewGroupByRequestCodeOrThrow(reviewRequestCode);
- return templateMapper.mapToTemplateResponse(reviewGroup);
+ ReviewGroup reviewGroup = reviewGroupService.getReviewGroupByReviewRequestCode(reviewRequestCode);
+ Template template = templateRepository.findById(reviewGroup.getTemplateId())
+ .orElseThrow(() -> new TemplateNotFoundByReviewGroupException(
+ reviewGroup.getId(), reviewGroup.getTemplateId())
+ );
+ return TemplateResponse.of(reviewGroup, template);
}
- private ReviewGroup findReviewGroupByRequestCodeOrThrow(String reviewRequestCode) {
- return reviewGroupRepository.findByReviewRequestCode(reviewRequestCode)
- .orElseThrow(() -> new ReviewGroupNotFoundByReviewRequestCodeException(reviewRequestCode));
+ @Transactional(readOnly = true)
+ public SectionNamesResponse getSectionNames(ReviewGroup reviewGroup) {
+ Template template = templateRepository.findById(reviewGroup.getTemplateId())
+ .orElseThrow(() -> new TemplateNotFoundByReviewGroupException(
+ reviewGroup.getId(), reviewGroup.getTemplateId())
+ );
+ return SectionNamesResponse.from(template);
}
}
diff --git a/backend/src/main/java/reviewme/template/service/dto/response/OptionGroupResponse.java b/backend/src/main/java/reviewme/template/service/dto/response/OptionGroupResponse.java
index c46f38148..6009ea82e 100644
--- a/backend/src/main/java/reviewme/template/service/dto/response/OptionGroupResponse.java
+++ b/backend/src/main/java/reviewme/template/service/dto/response/OptionGroupResponse.java
@@ -1,6 +1,8 @@
package reviewme.template.service.dto.response;
import java.util.List;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.SelectionRange;
public record OptionGroupResponse(
long optionGroupId,
@@ -8,4 +10,20 @@ public record OptionGroupResponse(
int maxCount,
List options
) {
+
+ public static OptionGroupResponse from(OptionGroup optionGroup) {
+ List optionItemResponses = optionGroup.getOptionItems()
+ .stream()
+ .map(OptionItemResponse::from)
+ .toList();
+
+ SelectionRange selectionRange = optionGroup.getSelectionRange();
+
+ return new OptionGroupResponse(
+ optionGroup.getId(),
+ selectionRange.getMinSelectionCount(),
+ selectionRange.getMaxSelectionCount(),
+ optionItemResponses
+ );
+ }
}
diff --git a/backend/src/main/java/reviewme/template/service/dto/response/OptionItemResponse.java b/backend/src/main/java/reviewme/template/service/dto/response/OptionItemResponse.java
index b9e456989..b70dc5817 100644
--- a/backend/src/main/java/reviewme/template/service/dto/response/OptionItemResponse.java
+++ b/backend/src/main/java/reviewme/template/service/dto/response/OptionItemResponse.java
@@ -1,7 +1,13 @@
package reviewme.template.service.dto.response;
+import reviewme.template.domain.OptionItem;
+
public record OptionItemResponse(
long optionId,
String content
) {
+
+ public static OptionItemResponse from(OptionItem optionItem) {
+ return new OptionItemResponse(optionItem.getId(), optionItem.getContent());
+ }
}
diff --git a/backend/src/main/java/reviewme/template/service/dto/response/QuestionResponse.java b/backend/src/main/java/reviewme/template/service/dto/response/QuestionResponse.java
index 90d1fb45e..ca0fef14e 100644
--- a/backend/src/main/java/reviewme/template/service/dto/response/QuestionResponse.java
+++ b/backend/src/main/java/reviewme/template/service/dto/response/QuestionResponse.java
@@ -1,14 +1,28 @@
package reviewme.template.service.dto.response;
import jakarta.annotation.Nullable;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
public record QuestionResponse(
long questionId,
boolean required,
String content,
- String questionType,
+ QuestionType questionType,
@Nullable OptionGroupResponse optionGroup,
boolean hasGuideline,
@Nullable String guideline
) {
+
+ public static QuestionResponse from(Question question) {
+ return new QuestionResponse(
+ question.getId(),
+ question.isRequired(),
+ question.getContent(),
+ question.getQuestionType(),
+ question.isCheckbox() ? OptionGroupResponse.from(question.getOptionGroup()) : null,
+ question.hasGuideline(),
+ question.getGuideline()
+ );
+ }
}
diff --git a/backend/src/main/java/reviewme/template/service/dto/response/SectionNamesResponse.java b/backend/src/main/java/reviewme/template/service/dto/response/SectionNamesResponse.java
index 6b1fae53c..04163dcc6 100644
--- a/backend/src/main/java/reviewme/template/service/dto/response/SectionNamesResponse.java
+++ b/backend/src/main/java/reviewme/template/service/dto/response/SectionNamesResponse.java
@@ -1,8 +1,17 @@
package reviewme.template.service.dto.response;
import java.util.List;
+import reviewme.template.domain.Template;
public record SectionNamesResponse(
List sections
) {
+
+ public static SectionNamesResponse from(Template template) {
+ List sectionNames = template.getSections()
+ .stream()
+ .map(section -> new SectionNameResponse(section.getId(), section.getSectionName()))
+ .toList();
+ return new SectionNamesResponse(sectionNames);
+ }
}
diff --git a/backend/src/main/java/reviewme/template/service/dto/response/SectionResponse.java b/backend/src/main/java/reviewme/template/service/dto/response/SectionResponse.java
index 31ae9d849..4884c7a02 100644
--- a/backend/src/main/java/reviewme/template/service/dto/response/SectionResponse.java
+++ b/backend/src/main/java/reviewme/template/service/dto/response/SectionResponse.java
@@ -2,13 +2,31 @@
import jakarta.annotation.Nullable;
import java.util.List;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.VisibleType;
public record SectionResponse(
long sectionId,
String sectionName,
- String visible,
+ VisibleType visible,
@Nullable Long onSelectedOptionId,
String header,
List questions
) {
+
+ public static SectionResponse from(Section section) {
+ List questionResponses = section.getQuestions()
+ .stream()
+ .map(QuestionResponse::from)
+ .toList();
+
+ return new SectionResponse(
+ section.getId(),
+ section.getSectionName(),
+ section.getVisibleType(),
+ section.isConditional() ? section.getOnSelectedOption().getId() : null,
+ section.getHeader(),
+ questionResponses
+ );
+ }
}
diff --git a/backend/src/main/java/reviewme/template/service/dto/response/TemplateResponse.java b/backend/src/main/java/reviewme/template/service/dto/response/TemplateResponse.java
index 35575ca26..8826f38e4 100644
--- a/backend/src/main/java/reviewme/template/service/dto/response/TemplateResponse.java
+++ b/backend/src/main/java/reviewme/template/service/dto/response/TemplateResponse.java
@@ -1,6 +1,8 @@
package reviewme.template.service.dto.response;
import java.util.List;
+import reviewme.reviewgroup.domain.ReviewGroup;
+import reviewme.template.domain.Template;
public record TemplateResponse(
long formId,
@@ -8,4 +10,18 @@ public record TemplateResponse(
String projectName,
List sections
) {
+
+ public static TemplateResponse of(ReviewGroup reviewGroup, Template template) {
+ List sectionResponses = template.getSections()
+ .stream()
+ .map(SectionResponse::from)
+ .toList();
+
+ return new TemplateResponse(
+ reviewGroup.getTemplateId(),
+ reviewGroup.getReviewee(),
+ reviewGroup.getProjectName(),
+ sectionResponses
+ );
+ }
}
diff --git a/backend/src/main/java/reviewme/template/service/exception/QuestionInSectionNotFoundException.java b/backend/src/main/java/reviewme/template/service/exception/QuestionInSectionNotFoundException.java
deleted file mode 100644
index d8dc6314b..000000000
--- a/backend/src/main/java/reviewme/template/service/exception/QuestionInSectionNotFoundException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package reviewme.template.service.exception;
-
-import lombok.extern.slf4j.Slf4j;
-import reviewme.global.exception.DataInconsistencyException;
-
-@Slf4j
-public class QuestionInSectionNotFoundException extends DataInconsistencyException {
-
- public QuestionInSectionNotFoundException(long sectionId, long questionId) {
- super("서버 내부에 문제가 발생했습니다. 잠시 후 다시 시도해주세요.");
- log.error("Question in section not found - sectionId: {}, questionId: {}", sectionId, questionId, this);
- }
-}
diff --git a/backend/src/main/java/reviewme/template/service/mapper/TemplateMapper.java b/backend/src/main/java/reviewme/template/service/mapper/TemplateMapper.java
deleted file mode 100644
index 02b6084f1..000000000
--- a/backend/src/main/java/reviewme/template/service/mapper/TemplateMapper.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package reviewme.template.service.mapper;
-
-import java.util.List;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Component;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
-import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.SectionQuestion;
-import reviewme.template.domain.Template;
-import reviewme.template.domain.TemplateSection;
-import reviewme.template.service.exception.MissingOptionItemsInOptionGroupException;
-import reviewme.template.service.exception.SectionInTemplateNotFoundException;
-import reviewme.template.service.exception.TemplateNotFoundByReviewGroupException;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
-import reviewme.template.service.dto.response.OptionGroupResponse;
-import reviewme.template.service.dto.response.OptionItemResponse;
-import reviewme.template.service.dto.response.QuestionResponse;
-import reviewme.template.service.dto.response.SectionResponse;
-import reviewme.template.service.dto.response.TemplateResponse;
-import reviewme.template.service.exception.QuestionInSectionNotFoundException;
-
-@Component
-@RequiredArgsConstructor
-public class TemplateMapper {
-
- public static final String REVIEWEE_NAME_PLACEHOLDER = "{revieweeName}";
-
- private final TemplateRepository templateRepository;
- private final SectionRepository sectionRepository;
- private final QuestionRepository questionRepository;
- private final OptionGroupRepository optionGroupRepository;
- private final OptionItemRepository optionItemRepository;
-
- public TemplateResponse mapToTemplateResponse(ReviewGroup reviewGroup) {
- Template template = templateRepository.findById(reviewGroup.getTemplateId())
- .orElseThrow(() -> new TemplateNotFoundByReviewGroupException(
- reviewGroup.getId(), reviewGroup.getTemplateId()
- ));
-
- List sectionResponses = template.getSectionIds()
- .stream()
- .map(this::mapToSectionResponse)
- .toList();
-
- return new TemplateResponse(
- template.getId(),
- reviewGroup.getReviewee(),
- reviewGroup.getProjectName(),
- sectionResponses
- );
- }
-
- private SectionResponse mapToSectionResponse(TemplateSection templateSection) {
- Section section = sectionRepository.findById(templateSection.getSectionId())
- .orElseThrow(() -> new SectionInTemplateNotFoundException(
- templateSection.getTemplateId(), templateSection.getSectionId())
- );
- List questionResponses = section.getQuestionIds()
- .stream()
- .map(this::mapToQuestionResponse)
- .toList();
-
- return new SectionResponse(
- section.getId(),
- section.getSectionName(),
- section.getVisibleType().name(),
- section.getOnSelectedOptionId(),
- section.getHeader(),
- questionResponses
- );
- }
-
- private QuestionResponse mapToQuestionResponse(SectionQuestion sectionQuestion) {
- Question question = questionRepository.findById(sectionQuestion.getQuestionId())
- .orElseThrow(() -> new QuestionInSectionNotFoundException(
- sectionQuestion.getSectionId(), sectionQuestion.getQuestionId())
- );
- OptionGroupResponse optionGroupResponse = optionGroupRepository.findByQuestionId(question.getId())
- .map(this::mapToOptionGroupResponse)
- .orElse(null);
-
- return new QuestionResponse(
- question.getId(),
- question.isRequired(),
- question.getContent(),
- question.getQuestionType().name(),
- optionGroupResponse,
- question.hasGuideline(),
- question.getGuideline()
- );
- }
-
- private OptionGroupResponse mapToOptionGroupResponse(OptionGroup optionGroup) {
- List optionItems = optionItemRepository.findAllByOptionGroupId(optionGroup.getId());
- if (optionItems.isEmpty()) {
- throw new MissingOptionItemsInOptionGroupException(optionGroup.getId());
- }
-
- List optionItemResponses = optionItems.stream()
- .map(this::mapToOptionItemResponse)
- .toList();
-
- return new OptionGroupResponse(
- optionGroup.getId(),
- optionGroup.getMinSelectionCount(),
- optionGroup.getMaxSelectionCount(),
- optionItemResponses
- );
- }
-
- private OptionItemResponse mapToOptionItemResponse(OptionItem optionItem) {
- return new OptionItemResponse(optionItem.getId(), optionItem.getContent());
- }
-}
diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml
index aa0160b1f..45df6e2cb 100644
--- a/backend/src/main/resources/application.yml
+++ b/backend/src/main/resources/application.yml
@@ -37,9 +37,3 @@ cors:
allowed-origins:
- http://localhost
- https://localhost
-
-request-limit:
- threshold: 3
- duration: 1s
- host: localhost
- port: 6379
diff --git a/backend/src/main/resources/db/migration/V4__template_association.sql b/backend/src/main/resources/db/migration/V4__template_association.sql
new file mode 100644
index 000000000..440768ed9
--- /dev/null
+++ b/backend/src/main/resources/db/migration/V4__template_association.sql
@@ -0,0 +1,19 @@
+-- 기존 Template의 중간 테이블을 사용하지 않도록 수정합니다.
+-- 1:N이 됨에 따라, N 부분에 foreign key가 필요합니다.
+
+ALTER TABLE section ADD COLUMN template_id BIGINT;
+ALTER TABLE question ADD COLUMN section_id BIGINT;
+ALTER TABLE question ADD COLUMN option_group_id BIGINT;
+
+-- 기존 테이블의 데이터를 새로운 테이블로 이동
+UPDATE question q JOIN section_question sq ON q.id = sq.question_id SET q.section_id = sq.section_id;
+UPDATE section s JOIN template_section st ON s.id = st.section_id SET s.template_id = st.template_id;
+UPDATE question q JOIN option_group og ON q.id = og.question_id SET q.option_group_id = og.id;
+
+-- FK 관계 설정
+ALTER TABLE section ADD CONSTRAINT section_fk_template_id FOREIGN KEY (template_id) REFERENCES template (id);
+ALTER TABLE section ADD CONSTRAINT section_fk_on_selected_option_id FOREIGN KEY (on_selected_option_id) REFERENCES option_item (id);
+ALTER TABLE question ADD CONSTRAINT question_fk_section_id FOREIGN KEY (section_id) REFERENCES section (id);
+ALTER TABLE option_item ADD CONSTRAINT option_item_fk_option_group_id FOREIGN KEY (option_group_id) REFERENCES option_group (id);
+
+-- 혹시 몰라서 DROP TABLE은 하지 않음
diff --git a/backend/src/main/resources/ports.yml b/backend/src/main/resources/ports.yml
deleted file mode 100644
index 8b8093829..000000000
--- a/backend/src/main/resources/ports.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-server:
- port: ${SERVER_PORT}
-
-management:
- server:
- port: ${ACTUATOR_PORT}
diff --git a/backend/src/test/java/reviewme/api/ApiTest.java b/backend/src/test/java/reviewme/api/ApiTest.java
index 682a2ea18..8eb10c5ed 100644
--- a/backend/src/test/java/reviewme/api/ApiTest.java
+++ b/backend/src/test/java/reviewme/api/ApiTest.java
@@ -1,7 +1,5 @@
package reviewme.api;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyHeaders;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris;
@@ -17,11 +15,8 @@
import org.apache.http.HttpHeaders;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.MediaType;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
@@ -31,11 +26,15 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
+import reviewme.auth.controller.AuthController;
+import reviewme.auth.service.AuthService;
import reviewme.highlight.controller.HighlightController;
import reviewme.highlight.service.HighlightService;
+import reviewme.member.controller.MemberController;
+import reviewme.member.service.MemberService;
import reviewme.review.controller.ReviewController;
-import reviewme.review.service.ReviewGatheredLookupService;
import reviewme.review.service.ReviewDetailLookupService;
+import reviewme.review.service.ReviewGatheredLookupService;
import reviewme.review.service.ReviewListLookupService;
import reviewme.review.service.ReviewRegisterService;
import reviewme.review.service.ReviewSummaryService;
@@ -45,7 +44,6 @@
import reviewme.reviewgroup.service.ReviewGroupService;
import reviewme.template.controller.SectionController;
import reviewme.template.controller.TemplateController;
-import reviewme.template.service.SectionService;
import reviewme.template.service.TemplateService;
@WebMvcTest({
@@ -53,7 +51,9 @@
ReviewController.class,
TemplateController.class,
SectionController.class,
- HighlightController.class
+ HighlightController.class,
+ MemberController.class,
+ AuthController.class
})
@ExtendWith(RestDocumentationExtension.class)
public abstract class ApiTest {
@@ -79,22 +79,19 @@ public abstract class ApiTest {
protected ReviewGroupLookupService reviewGroupLookupService;
@MockBean
- protected RedisTemplate redisTemplate;
-
- @Mock
- protected ValueOperations valueOperations;
+ protected ReviewSummaryService reviewSummaryService;
@MockBean
- protected ReviewSummaryService reviewSummaryService;
+ protected ReviewGatheredLookupService reviewGatheredLookupService;
@MockBean
- protected SectionService sectionService;
+ protected HighlightService highlightService;
@MockBean
- protected ReviewGatheredLookupService reviewGatheredLookupService;
+ protected MemberService memberService;
@MockBean
- protected HighlightService highlightService;
+ protected AuthService authService;
@MockBean
private ReviewGroupSessionResolver reviewGroupSessionResolver;
@@ -111,12 +108,6 @@ public abstract class ApiTest {
}
};
- @BeforeEach
- void setUpRedisConfig() {
- given(redisTemplate.opsForValue()).willReturn(valueOperations);
- given(valueOperations.increment(anyString())).willReturn(1L);
- }
-
@BeforeEach
void setUpRestDocs(WebApplicationContext context, RestDocumentationContextProvider provider) {
UriModifyingOperationPreprocessor uriModifier = modifyUris()
diff --git a/backend/src/test/java/reviewme/api/AuthApiTest.java b/backend/src/test/java/reviewme/api/AuthApiTest.java
new file mode 100644
index 000000000..3bf6c61c9
--- /dev/null
+++ b/backend/src/test/java/reviewme/api/AuthApiTest.java
@@ -0,0 +1,59 @@
+package reviewme.api;
+
+import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
+import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
+import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.restdocs.cookies.CookieDescriptor;
+import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
+import org.springframework.restdocs.payload.FieldDescriptor;
+
+public class AuthApiTest extends ApiTest {
+
+ @Test
+ void 깃허브로_인증한다() {
+ String request = """
+ {
+ "code": "github_auth_code"
+ }
+ """;
+
+ FieldDescriptor[] requestFieldDescriptors = {
+ fieldWithPath("code").description("깃허브 임시 인증 코드"),
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "github-auth",
+ requestFields(requestFieldDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .body(request)
+ .when().post("/v2/auth/github")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(200);
+ }
+
+ @Test
+ void 로그아웃한다() {
+ CookieDescriptor[] cookieDescriptors = {
+ cookieWithName("JSESSIONID").description("세션 ID")
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "logout",
+ requestCookies(cookieDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .cookie("JSESSIONID", "SESSION12345678")
+ .when().post("/v2/auth/logout")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(204);
+ }
+}
diff --git a/backend/src/test/java/reviewme/api/MemberApiTest.java b/backend/src/test/java/reviewme/api/MemberApiTest.java
new file mode 100644
index 000000000..d8b4d4191
--- /dev/null
+++ b/backend/src/test/java/reviewme/api/MemberApiTest.java
@@ -0,0 +1,45 @@
+package reviewme.api;
+
+import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
+import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
+import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
+import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
+import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
+
+import org.junit.jupiter.api.Test;
+import org.mockito.BDDMockito;
+import org.springframework.restdocs.cookies.CookieDescriptor;
+import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
+import org.springframework.restdocs.payload.FieldDescriptor;
+import reviewme.member.service.dto.ProfileResponse;
+
+public class MemberApiTest extends ApiTest {
+
+ @Test
+ void 내_프로필을_불러온다() {
+ BDDMockito.given(memberService.getProfile())
+ .willReturn(new ProfileResponse("donghoony", "https://aru.image"));
+
+ CookieDescriptor[] cookieDescriptors = {
+ cookieWithName("JSESSIONID").description("세션 ID")
+ };
+
+ FieldDescriptor[] responseFieldDescriptors = {
+ fieldWithPath("nickname").description("닉네임"),
+ fieldWithPath("profileImageUrl").description("프로필 이미지 URL")
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "my-profile",
+ requestCookies(cookieDescriptors),
+ responseFields(responseFieldDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .cookie("JSESSIONID", "SESSION12345678")
+ .when().get("/v2/members/profile")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(200);
+ }
+}
diff --git a/backend/src/test/java/reviewme/api/ReviewApiTest.java b/backend/src/test/java/reviewme/api/ReviewApiTest.java
index 5add4cfbd..5d354a3a7 100644
--- a/backend/src/test/java/reviewme/api/ReviewApiTest.java
+++ b/backend/src/test/java/reviewme/api/ReviewApiTest.java
@@ -3,7 +3,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
@@ -22,7 +21,6 @@
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.request.ParameterDescriptor;
-import reviewme.question.domain.QuestionType;
import reviewme.review.service.dto.request.ReviewRegisterRequest;
import reviewme.review.service.dto.response.gathered.HighlightResponse;
import reviewme.review.service.dto.response.gathered.RangeResponse;
@@ -31,11 +29,14 @@
import reviewme.review.service.dto.response.gathered.SimpleQuestionResponse;
import reviewme.review.service.dto.response.gathered.TextResponse;
import reviewme.review.service.dto.response.gathered.VoteResponse;
-import reviewme.review.service.dto.response.list.ReceivedReviewsResponse;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageResponse;
import reviewme.review.service.dto.response.list.ReceivedReviewsSummaryResponse;
import reviewme.review.service.dto.response.list.ReviewCategoryResponse;
-import reviewme.review.service.dto.response.list.ReviewListElementResponse;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageElementResponse;
+import reviewme.review.service.dto.response.list.AuthoredReviewElementResponse;
+import reviewme.review.service.dto.response.list.AuthoredReviewsResponse;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.template.domain.QuestionType;
class ReviewApiTest extends ApiTest {
@@ -56,7 +57,7 @@ class ReviewApiTest extends ApiTest {
""";
@Test
- void 리뷰를_등록한다() {
+ void 비회원이_리뷰를_등록한다() {
BDDMockito.given(reviewRegisterService.registerReview(any(ReviewRegisterRequest.class)))
.willReturn(1L);
@@ -70,7 +71,7 @@ class ReviewApiTest extends ApiTest {
};
RestDocumentationResultHandler handler = document(
- "create-review",
+ "create-review-by-guest",
requestFields(requestFieldDescriptors)
);
@@ -82,6 +83,39 @@ class ReviewApiTest extends ApiTest {
.statusCode(201);
}
+ @Test
+ void 회원이_리뷰를_등록한다() {
+ BDDMockito.given(reviewRegisterService.registerReview(any(ReviewRegisterRequest.class)))
+ .willReturn(1L);
+
+ CookieDescriptor[] cookieDescriptors = {
+ cookieWithName("JSESSIONID").description("세션 ID")
+ };
+
+ FieldDescriptor[] requestFieldDescriptors = {
+ fieldWithPath("reviewRequestCode").description("리뷰 요청 코드"),
+
+ fieldWithPath("answers[]").description("답변 목록"),
+ fieldWithPath("answers[].questionId").description("질문 ID"),
+ fieldWithPath("answers[].selectedOptionIds").description("선택한 옵션 ID 목록").optional(),
+ fieldWithPath("answers[].text").description("서술 답변").optional()
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "create-review-by-member",
+ requestCookies(cookieDescriptors),
+ requestFields(requestFieldDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .cookie("JSESSIONID", "ASVNE1VAKDNV4")
+ .body(request)
+ .when().post("/v2/reviews")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(201);
+ }
+
@Test
void 리뷰_그룹_코드가_올바르지_않은_경우_예외가_발생한다() {
BDDMockito.given(reviewRegisterService.registerReview(any(ReviewRegisterRequest.class)))
@@ -132,22 +166,17 @@ class ReviewApiTest extends ApiTest {
fieldWithPath("sections[].sectionId").description("섹션 ID"),
fieldWithPath("sections[].header").description("섹션 제목"),
- fieldWithPath("sections[].questions[]").description("질문 목록"),
- fieldWithPath("sections[].questions[].questionId").description("질문 ID"),
- fieldWithPath("sections[].questions[].required").description("필수 여부"),
- fieldWithPath("sections[].questions[].content").description("질문 내용"),
- fieldWithPath("sections[].questions[].questionType").description("질문 타입"),
-
- fieldWithPath("sections[].questions[].optionGroup").description("옵션 그룹").optional(),
- fieldWithPath("sections[].questions[].optionGroup.optionGroupId").description("옵션 그룹 ID"),
- fieldWithPath("sections[].questions[].optionGroup.minCount").description("최소 선택 개수"),
- fieldWithPath("sections[].questions[].optionGroup.maxCount").description("최대 선택 개수"),
-
- fieldWithPath("sections[].questions[].optionGroup.options[]").description("선택 항목 목록"),
- fieldWithPath("sections[].questions[].optionGroup.options[].optionId").description("선택 항목 ID"),
- fieldWithPath("sections[].questions[].optionGroup.options[].content").description("선택 항목 내용"),
- fieldWithPath("sections[].questions[].optionGroup.options[].isChecked").description("선택 여부"),
- fieldWithPath("sections[].questions[].answer").description("서술형 답변").optional(),
+ fieldWithPath("sections[].reviews[]").description("리뷰 목록"),
+ fieldWithPath("sections[].reviews[].questionId").description("질문 ID"),
+ fieldWithPath("sections[].reviews[].required").description("필수 여부"),
+ fieldWithPath("sections[].reviews[].questionContents").description("질문 내용"),
+ fieldWithPath("sections[].reviews[].questionType").description("질문 타입"),
+
+ fieldWithPath("sections[].reviews[].options").description("선택 항목 목록").optional(),
+ fieldWithPath("sections[].reviews[].options[]").description("선택 항목 목록"),
+ fieldWithPath("sections[].reviews[].options[].optionId").description("선택 항목 ID"),
+ fieldWithPath("sections[].reviews[].options[].content").description("선택 항목 내용"),
+ fieldWithPath("sections[].reviews[].answer").description("서술형 답변").optional(),
};
RestDocumentationResultHandler handler = document(
@@ -168,13 +197,13 @@ class ReviewApiTest extends ApiTest {
@Test
void 자신이_받은_리뷰_목록을_조회한다() {
- List receivedReviews = List.of(
- new ReviewListElementResponse(1L, LocalDate.of(2024, 8, 1), "(리뷰 미리보기 1)",
+ List receivedReviews = List.of(
+ new ReceivedReviewPageElementResponse(1L, LocalDate.of(2024, 8, 1), "(리뷰 미리보기 1)",
List.of(new ReviewCategoryResponse(1L, "카테고리 1"))),
- new ReviewListElementResponse(2L, LocalDate.of(2024, 8, 2), "(리뷰 미리보기 2)",
+ new ReceivedReviewPageElementResponse(2L, LocalDate.of(2024, 8, 2), "(리뷰 미리보기 2)",
List.of(new ReviewCategoryResponse(2L, "카테고리 2")))
);
- ReceivedReviewsResponse response = new ReceivedReviewsResponse(
+ ReceivedReviewPageResponse response = new ReceivedReviewPageResponse(
"아루3", "리뷰미", 1L, true, receivedReviews);
BDDMockito.given(reviewListLookupService.getReceivedReviews(anyLong(), anyInt(), any()))
.willReturn(response);
@@ -217,7 +246,7 @@ class ReviewApiTest extends ApiTest {
.queryParam("reviewRequestCode", "hello!!")
.queryParam("lastReviewId", "2")
.queryParam("size", "5")
- .when().get("/v2/reviews")
+ .when().get("/v2/reviews/received")
.then().log().all()
.apply(handler)
.statusCode(200);
@@ -315,4 +344,59 @@ class ReviewApiTest extends ApiTest {
.apply(handler)
.statusCode(200);
}
+
+ @Test
+ void 자신이_작성한_리뷰_목록을_조회한다() {
+ List authoredReviews = List.of(
+ new AuthoredReviewElementResponse(1L, "테드1", "리뷰미", LocalDate.of(2024, 8, 2), "(리뷰 미리보기 1)",
+ List.of(new ReviewCategoryResponse(1L, "카테고리 1"))),
+ new AuthoredReviewElementResponse(2L, "테드2", "리뷰미", LocalDate.of(2024, 8, 1), "(리뷰 미리보기 2)",
+ List.of(new ReviewCategoryResponse(2L, "카테고리 2")))
+ );
+ AuthoredReviewsResponse response = new AuthoredReviewsResponse(authoredReviews, 1L, true);
+ BDDMockito.given(reviewListLookupService.getAuthoredReviews(anyLong(), anyInt()))
+ .willReturn(response);
+
+ CookieDescriptor[] cookieDescriptors = {
+ cookieWithName("JSESSIONID").description("세션 ID")
+ };
+
+ ParameterDescriptor[] queryParameter = {
+ parameterWithName("lastReviewId").description("페이지의 마지막 리뷰 ID - 기본으로 최신순 첫번째 페이지 응답"),
+ parameterWithName("size").description("페이지의 크기 - 기본으로 10개씩 응답")
+ };
+
+ FieldDescriptor[] responseFieldDescriptors = {
+ fieldWithPath("lastReviewId").description("페이지의 마지막 리뷰 ID"),
+ fieldWithPath("isLastPage").description("마지막 페이지 여부"),
+
+ fieldWithPath("reviews[]").description("리뷰 목록 (생성일 기준 내림차순 정렬)"),
+ fieldWithPath("reviews[].reviewId").description("리뷰 ID"),
+ fieldWithPath("reviews[].createdAt").description("리뷰 작성 날짜"),
+ fieldWithPath("reviews[].contentPreview").description("리뷰 미리보기"),
+ fieldWithPath("reviews[].revieweeName").description("리뷰이 이름"),
+ fieldWithPath("reviews[].projectName").description("프로젝트명"),
+
+ fieldWithPath("reviews[].categories[]").description("카테고리 목록"),
+ fieldWithPath("reviews[].categories[].optionId").description("카테고리 ID"),
+ fieldWithPath("reviews[].categories[].content").description("카테고리 내용")
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "authored-review-list-with-pagination",
+ requestCookies(cookieDescriptors),
+ queryParameters(queryParameter),
+ responseFields(responseFieldDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .cookie("JSESSIONID", "ASVNE1VAKDNV4")
+// .queryParam("reviewRequestCode", "hello!!")
+ .queryParam("lastReviewId", "2")
+ .queryParam("size", "5")
+ .when().get("/v2/reviews/authored")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(200);
+ }
}
diff --git a/backend/src/test/java/reviewme/api/ReviewGroupApiTest.java b/backend/src/test/java/reviewme/api/ReviewGroupApiTest.java
index e87cb8b5c..7de76c4b0 100644
--- a/backend/src/test/java/reviewme/api/ReviewGroupApiTest.java
+++ b/backend/src/test/java/reviewme/api/ReviewGroupApiTest.java
@@ -4,6 +4,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.springframework.restdocs.cookies.CookieDocumentation.cookieWithName;
+import static org.springframework.restdocs.cookies.CookieDocumentation.requestCookies;
import static org.springframework.restdocs.cookies.CookieDocumentation.responseCookies;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
@@ -12,6 +13,8 @@
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
+import java.time.LocalDate;
+import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.springframework.restdocs.cookies.CookieDescriptor;
@@ -20,12 +23,14 @@
import org.springframework.restdocs.request.ParameterDescriptor;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationResponse;
+import reviewme.reviewgroup.service.dto.ReviewGroupPageElementResponse;
+import reviewme.reviewgroup.service.dto.ReviewGroupPageResponse;
import reviewme.reviewgroup.service.dto.ReviewGroupResponse;
class ReviewGroupApiTest extends ApiTest {
@Test
- void 리뷰_그룹을_생성한다() {
+ void 비회원용_리뷰_그룹을_생성한다() {
BDDMockito.given(reviewGroupService.createReviewGroup(any(ReviewGroupCreationRequest.class)))
.willReturn(new ReviewGroupCreationResponse("ABCD1234"));
@@ -48,7 +53,7 @@ class ReviewGroupApiTest extends ApiTest {
};
RestDocumentationResultHandler handler = document(
- "review-group-create",
+ "guest-review-group-create",
requestFields(requestFieldDescriptors),
responseFields(responseFieldDescriptors)
);
@@ -62,28 +67,99 @@ class ReviewGroupApiTest extends ApiTest {
}
@Test
- void 리뷰_요청_코드로_리뷰_그룹_정보를_반환한다() {
+ void 회원용_리뷰_그룹을_생성한다() {
+ BDDMockito.given(reviewGroupService.createReviewGroup(any(ReviewGroupCreationRequest.class)))
+ .willReturn(new ReviewGroupCreationResponse("ABCD1234"));
+
+ CookieDescriptor[] cookieDescriptors = {
+ cookieWithName("JSESSIONID").description("세션 ID")
+ };
+
+ String request = """
+ {
+ "revieweeName": "아루",
+ "projectName": "리뷰미"
+ }
+ """;
+
+ FieldDescriptor[] requestFieldDescriptors = {
+ fieldWithPath("revieweeName").description("리뷰이 이름"),
+ fieldWithPath("projectName").description("프로젝트 이름")
+ };
+
+ FieldDescriptor[] responseFieldDescriptors = {
+ fieldWithPath("reviewRequestCode").description("리뷰 요청 코드")
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "member-review-group-create",
+ requestCookies(cookieDescriptors),
+ requestFields(requestFieldDescriptors),
+ responseFields(responseFieldDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .cookie("JSESSIONID", "ASVNE1VAKDNV4")
+ .body(request)
+ .when().post("/v2/groups")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(200);
+ }
+
+ @Test
+ void 리뷰_요청_코드로_회원이_만든_리뷰_그룹_정보를_반환한다() {
BDDMockito.given(reviewGroupLookupService.getReviewGroupSummary(anyString()))
- .willReturn(new ReviewGroupResponse("아루", "리뷰미"));
+ .willReturn(new ReviewGroupResponse(1L,"아루", "리뷰미"));
ParameterDescriptor[] parameterDescriptors = {
parameterWithName("reviewRequestCode").description("리뷰 요청 코드")
};
FieldDescriptor[] responseFieldDescriptors = {
+ fieldWithPath("revieweeId").description("리뷰이 ID"),
fieldWithPath("revieweeName").description("리뷰이 이름"),
fieldWithPath("projectName").description("프로젝트 이름")
};
RestDocumentationResultHandler handler = document(
- "review-group-summary",
+ "member-review-group-summary",
queryParameters(parameterDescriptors),
responseFields(responseFieldDescriptors)
);
givenWithSpec().log().all()
.queryParam("reviewRequestCode", "ABCD1234")
- .when().get("/v2/groups")
+ .when().get("/v2/groups/summary")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(200);
+ }
+
+ @Test
+ void 리뷰_요청_코드로_비회원이_만든_리뷰_그룹_정보를_반환한다() {
+ BDDMockito.given(reviewGroupLookupService.getReviewGroupSummary(anyString()))
+ .willReturn(new ReviewGroupResponse(null, "아루", "리뷰미"));
+
+ ParameterDescriptor[] parameterDescriptors = {
+ parameterWithName("reviewRequestCode").description("리뷰 요청 코드")
+ };
+
+ FieldDescriptor[] responseFieldDescriptors = {
+ fieldWithPath("revieweeId").description("리뷰이 ID"),
+ fieldWithPath("revieweeName").description("리뷰이 이름"),
+ fieldWithPath("projectName").description("프로젝트 이름")
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "guest-review-group-summary",
+ queryParameters(parameterDescriptors),
+ responseFields(responseFieldDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .queryParam("reviewRequestCode", "ABCD1234")
+ .when().get("/v2/groups/summary")
.then().log().all()
.apply(handler)
.statusCode(200);
@@ -121,4 +197,44 @@ class ReviewGroupApiTest extends ApiTest {
.cookie("JSESSIONID")
.statusCode(204);
}
+
+ @Test
+ void 회원이_생성한_프로젝트_목록을_반환한다() {
+ ReviewGroupPageResponse response = new ReviewGroupPageResponse(2L, true,
+ List.of(
+ new ReviewGroupPageElementResponse("이동훈", "우테코", LocalDate.of(2024, 1, 30), "WOOTECO1", 1),
+ new ReviewGroupPageElementResponse("아루", "리뷰미", LocalDate.of(2024, 1, 5), "ABCD1234", 2)
+ )
+ );
+ BDDMockito.given(reviewGroupLookupService.getMyReviewGroups())
+ .willReturn(response);
+
+ CookieDescriptor[] cookieDescriptors = {
+ cookieWithName("JSESSIONID").description("세션 ID")
+ };
+
+ FieldDescriptor[] responseFieldDescriptors = {
+ fieldWithPath("lastReviewGroupId").description("해당 페이지의 마지막 리뷰 그룹 ID"),
+ fieldWithPath("isLastPage").description("마지막 페이지 여부"),
+ fieldWithPath("reviewGroups[]").description("리뷰 그룹 목록 (생성일 기준 내림차순 정렬)"),
+ fieldWithPath("reviewGroups[].revieweeName").description("리뷰이 이름"),
+ fieldWithPath("reviewGroups[].projectName").description("프로젝트 이름"),
+ fieldWithPath("reviewGroups[].createdAt").description("생성일"),
+ fieldWithPath("reviewGroups[].reviewRequestCode").description("리뷰 요청 코드"),
+ fieldWithPath("reviewGroups[].reviewCount").description("작성된 리뷰 수")
+ };
+
+ RestDocumentationResultHandler handler = document(
+ "review-group-list",
+ responseFields(responseFieldDescriptors),
+ requestCookies(cookieDescriptors)
+ );
+
+ givenWithSpec().log().all()
+ .cookie("JSESSIONID", "ABCDEFGHI1234")
+ .when().get("/v2/groups")
+ .then().log().all()
+ .apply(handler)
+ .statusCode(200);
+ }
}
diff --git a/backend/src/test/java/reviewme/api/TemplateApiTest.java b/backend/src/test/java/reviewme/api/TemplateApiTest.java
index 932039bac..7a323f59f 100644
--- a/backend/src/test/java/reviewme/api/TemplateApiTest.java
+++ b/backend/src/test/java/reviewme/api/TemplateApiTest.java
@@ -17,7 +17,7 @@
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.restdocs.payload.FieldDescriptor;
import org.springframework.restdocs.request.ParameterDescriptor;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.template.service.dto.response.SectionNameResponse;
import reviewme.template.service.dto.response.SectionNamesResponse;
@@ -104,7 +104,7 @@ class TemplateApiTest extends ApiTest {
new SectionNameResponse(1, "섹션1 이름"),
new SectionNameResponse(2, "섹션2 이름")
));
- BDDMockito.given(sectionService.getSectionNames(any()))
+ BDDMockito.given(templateService.getSectionNames(any()))
.willReturn(response);
CookieDescriptor[] cookieDescriptors = {
diff --git a/backend/src/test/java/reviewme/api/TemplateFixture.java b/backend/src/test/java/reviewme/api/TemplateFixture.java
index aba719fbb..334e46c8e 100644
--- a/backend/src/test/java/reviewme/api/TemplateFixture.java
+++ b/backend/src/test/java/reviewme/api/TemplateFixture.java
@@ -2,12 +2,11 @@
import java.time.LocalDate;
import java.util.List;
-import reviewme.question.domain.QuestionType;
-import reviewme.review.service.dto.response.detail.OptionGroupAnswerResponse;
import reviewme.review.service.dto.response.detail.OptionItemAnswerResponse;
import reviewme.review.service.dto.response.detail.QuestionAnswerResponse;
-import reviewme.review.service.dto.response.detail.SectionAnswerResponse;
import reviewme.review.service.dto.response.detail.ReviewDetailResponse;
+import reviewme.review.service.dto.response.detail.SectionAnswerResponse;
+import reviewme.template.domain.QuestionType;
import reviewme.template.domain.VisibleType;
import reviewme.template.service.dto.response.OptionGroupResponse;
import reviewme.template.service.dto.response.OptionItemResponse;
@@ -29,14 +28,14 @@ public static TemplateResponse templateResponse() {
1,
true,
"프로젝트 기간 동안, 아루의 강점이 드러났던 순간을 선택해주세요.",
- QuestionType.CHECKBOX.name(),
+ QuestionType.CHECKBOX,
new OptionGroupResponse(1, 1, 2, firstSectionOptions),
false,
null
)
);
SectionResponse firstSection = new SectionResponse(
- 1, "카테고리 선택", VisibleType.ALWAYS.name(), null, "아루와 함께 한 기억을 떠올려볼게요.", firstSectionQuestions
+ 1, "카테고리 선택", VisibleType.ALWAYS, null, "아루와 함께 한 기억을 떠올려볼게요.", firstSectionQuestions
);
// Section 2
@@ -50,7 +49,7 @@ public static TemplateResponse templateResponse() {
2,
true,
"커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.",
- QuestionType.CHECKBOX.name(),
+ QuestionType.CHECKBOX,
new OptionGroupResponse(2, 1, 3, secondSectionOptions),
false,
null
@@ -59,14 +58,14 @@ public static TemplateResponse templateResponse() {
3,
true,
"위에서 선택한 사항에 대해 조금 더 자세히 설명해주세요.",
- QuestionType.TEXT.name(),
+ QuestionType.TEXT,
null,
true,
"상황을 자세하게 기록할수록 아루에게 도움이 돼요. 아루 덕분에 팀이 원활한 소통을 이뤘거나, 함께 일하면서 배울 점이 있었는지 떠올려 보세요."
)
);
SectionResponse secondSection = new SectionResponse(
- 2, "커뮤니케이션 능력", VisibleType.ALWAYS.name(), 1L, "아루의 커뮤니케이션, 협업 능력을 평가해주세요.", secondSectionQuestions
+ 2, "커뮤니케이션 능력", VisibleType.ALWAYS, 1L, "아루의 커뮤니케이션, 협업 능력을 평가해주세요.", secondSectionQuestions
);
return new TemplateResponse(1, "아루", "리뷰미", List.of(firstSection, secondSection));
@@ -74,36 +73,41 @@ public static TemplateResponse templateResponse() {
public static ReviewDetailResponse templateAnswerResponse() {
// Section 1
- List firstOptionAnswers = List.of(
- new OptionItemAnswerResponse(1, "커뮤니케이션, 협업 능력 (ex: 팀원간의 원활한 정보 공유, 명확한 의사소통)", true),
- new OptionItemAnswerResponse(2, "문제 해결 능력 (ex: 프로젝트 중 만난 버그/오류를 분석하고 이를 해결하는 능력)", false),
- new OptionItemAnswerResponse(3, "시간 관리 능력 (ex: 일정과 마감 기한 준수, 업무의 우선 순위 분배)", false)
+ List optionAnswer = List.of(
+ new OptionItemAnswerResponse(1, "커뮤니케이션, 협업 능력 (ex: 팀원간의 원활한 정보 공유, 명확한 의사소통)"),
+ new OptionItemAnswerResponse(2, "문제 해결 능력 (ex: 프로젝트 중 만난 버그/오류를 분석하고 이를 해결하는 능력)"),
+ new OptionItemAnswerResponse(3, "시간 관리 능력 (ex: 일정과 마감 기한 준수, 업무의 우선 순위 분배)")
);
- OptionGroupAnswerResponse firstOptionGroupAnswer = new OptionGroupAnswerResponse(1, 1, 2, firstOptionAnswers);
QuestionAnswerResponse firstQuestionAnswer = new QuestionAnswerResponse(
- 1, true, QuestionType.CHECKBOX, "프로젝트 기간 동안, 아루의 강점이 드러났던 순간을 선택해주세요.", firstOptionGroupAnswer, null
- );
- SectionAnswerResponse firstSectionAnswer = new SectionAnswerResponse(
- 1, "프로젝트 기간 동안, 아루의 강점이 드러났던 순간을 선택해주세요.", List.of(firstQuestionAnswer)
+ 1, true, QuestionType.CHECKBOX, "프로젝트 기간 동안, 아루의 강점이 드러났던 순간을 선택해주세요.", optionAnswer, null
);
- // Section 2
- List secondOptionAnswers = List.of(
- new OptionItemAnswerResponse(4, "반대 의견을 내더라도 듣는 사람이 기분 나쁘지 않게 이야기해요.", true),
- new OptionItemAnswerResponse(5, "팀원들의 의견을 잘 모아서 회의가 매끄럽게 진행되도록 해요.", false),
- new OptionItemAnswerResponse(6, "팀의 분위기를 주도해요.", true)
- );
- OptionGroupAnswerResponse secondOptionGroupAnswer = new OptionGroupAnswerResponse(2, 1, 3, secondOptionAnswers);
- QuestionAnswerResponse secondQuestionAnswer = new QuestionAnswerResponse(
- 2, true, QuestionType.CHECKBOX, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", secondOptionGroupAnswer,
- null
- );
- SectionAnswerResponse secondSectionAnswer = new SectionAnswerResponse(
- 2, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", List.of(secondQuestionAnswer)
+ QuestionAnswerResponse secondQuestionAnswer = new QuestionAnswerResponse(2, true, QuestionType.TEXT, "위에서 선택한 사항에 대해 조금 더 자세히 설명해주세요.", null, "나산초의 답변");
+
+ SectionAnswerResponse sectionAnswer = new SectionAnswerResponse(
+ 1, "프로젝트 기간 동안, 아루의 강점이 드러났던 순간을 선택해주세요.", List.of(firstQuestionAnswer, secondQuestionAnswer)
);
return new ReviewDetailResponse(
- 1, "아루", "리뷰미", LocalDate.of(2024, 8, 1), List.of(firstSectionAnswer, secondSectionAnswer)
+ 1, "아루", "리뷰미", LocalDate.of(2024, 8, 1), List.of(sectionAnswer)
);
+
+ // Section 2
+// List secondOptionAnswers = List.of(
+// new OptionItemAnswerResponse(4, "반대 의견을 내더라도 듣는 사람이 기분 나쁘지 않게 이야기해요."),
+// new OptionItemAnswerResponse(5, "팀원들의 의견을 잘 모아서 회의가 매끄럽게 진행되도록 해요."),
+// new OptionItemAnswerResponse(6, "팀의 분위기를 주도해요.")
+// );
+// QuestionAnswerResponse secondQuestionAnswer = new QuestionAnswerResponse(
+// 2, true, QuestionType.CHECKBOX, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", secondOptionAnswers,
+// null
+// );
+// SectionAnswerResponse secondSectionAnswer = new SectionAnswerResponse(
+// 2, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", List.of(secondQuestionAnswer)
+// );
+
+// return new ReviewDetailResponse(
+// 1, "아루", "리뷰미", LocalDate.of(2024, 8, 1), List.of(sectionAnswer, secondSectionAnswer)
+// );
}
}
diff --git a/backend/src/test/java/reviewme/config/TestConfig.java b/backend/src/test/java/reviewme/config/TestConfig.java
index f339dd641..e3a05bb95 100644
--- a/backend/src/test/java/reviewme/config/TestConfig.java
+++ b/backend/src/test/java/reviewme/config/TestConfig.java
@@ -1,7 +1,9 @@
package reviewme.config;
import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
+import reviewme.support.CacheCleaner;
import reviewme.support.DatabaseCleaner;
@TestConfiguration
@@ -11,4 +13,9 @@ public class TestConfig {
public DatabaseCleaner databaseCleaner() {
return new DatabaseCleaner();
}
+
+ @Bean
+ public CacheCleaner cacheCleaner(CacheManager cacheManager) {
+ return new CacheCleaner(cacheManager);
+ }
}
diff --git a/backend/src/test/java/reviewme/config/CorsConfigTest.java b/backend/src/test/java/reviewme/config/cors/CorsConfigTest.java
similarity index 93%
rename from backend/src/test/java/reviewme/config/CorsConfigTest.java
rename to backend/src/test/java/reviewme/config/cors/CorsConfigTest.java
index 90af4a342..d7f20bfd2 100644
--- a/backend/src/test/java/reviewme/config/CorsConfigTest.java
+++ b/backend/src/test/java/reviewme/config/cors/CorsConfigTest.java
@@ -1,4 +1,4 @@
-package reviewme.config;
+package reviewme.config.cors;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,6 +32,7 @@ void setUp() {
static class TestController {
@RequestMapping("/test")
public void test() {
+ // Testing controller calls, no-op
}
}
}
diff --git a/backend/src/test/java/reviewme/config/ExternalCorsConfigTest.java b/backend/src/test/java/reviewme/config/cors/ExternalCorsConfigTest.java
similarity index 98%
rename from backend/src/test/java/reviewme/config/ExternalCorsConfigTest.java
rename to backend/src/test/java/reviewme/config/cors/ExternalCorsConfigTest.java
index 095bb1bc7..39445b70f 100644
--- a/backend/src/test/java/reviewme/config/ExternalCorsConfigTest.java
+++ b/backend/src/test/java/reviewme/config/cors/ExternalCorsConfigTest.java
@@ -1,4 +1,4 @@
-package reviewme.config;
+package reviewme.config.cors;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
diff --git a/backend/src/test/java/reviewme/config/LocalCorsConfigTest.java b/backend/src/test/java/reviewme/config/cors/LocalCorsConfigTest.java
similarity index 97%
rename from backend/src/test/java/reviewme/config/LocalCorsConfigTest.java
rename to backend/src/test/java/reviewme/config/cors/LocalCorsConfigTest.java
index cd050b988..214de0857 100644
--- a/backend/src/test/java/reviewme/config/LocalCorsConfigTest.java
+++ b/backend/src/test/java/reviewme/config/cors/LocalCorsConfigTest.java
@@ -1,4 +1,4 @@
-package reviewme.config;
+package reviewme.config.cors;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
diff --git a/backend/src/test/java/reviewme/fixture/OptionGroupFixture.java b/backend/src/test/java/reviewme/fixture/OptionGroupFixture.java
index 259a3ebcf..faf47d813 100644
--- a/backend/src/test/java/reviewme/fixture/OptionGroupFixture.java
+++ b/backend/src/test/java/reviewme/fixture/OptionGroupFixture.java
@@ -1,10 +1,12 @@
package reviewme.fixture;
-import reviewme.question.domain.OptionGroup;
+import java.util.List;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
public class OptionGroupFixture {
- public static OptionGroup 선택지_그룹(long questionId) {
- return new OptionGroup(questionId, 1, 2);
+ public static OptionGroup 선택지_그룹(List optionItems) {
+ return new OptionGroup(optionItems, 1, optionItems.size());
}
}
diff --git a/backend/src/test/java/reviewme/fixture/OptionItemFixture.java b/backend/src/test/java/reviewme/fixture/OptionItemFixture.java
index 3b7e50725..5c43759e9 100644
--- a/backend/src/test/java/reviewme/fixture/OptionItemFixture.java
+++ b/backend/src/test/java/reviewme/fixture/OptionItemFixture.java
@@ -1,15 +1,11 @@
package reviewme.fixture;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.OptionType;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.OptionType;
public class OptionItemFixture {
- public static OptionItem 선택지(long optionGroupId) {
- return 선택지(optionGroupId, 1);
- }
-
- public static OptionItem 선택지(long optionGroupId, int position) {
- return new OptionItem("선택지 본문", optionGroupId, position, OptionType.CATEGORY);
+ public static OptionItem 선택지() {
+ return new OptionItem("선택지 본문", 1, OptionType.CATEGORY);
}
}
diff --git a/backend/src/test/java/reviewme/fixture/QuestionFixture.java b/backend/src/test/java/reviewme/fixture/QuestionFixture.java
index f4ce28b88..a225a9517 100644
--- a/backend/src/test/java/reviewme/fixture/QuestionFixture.java
+++ b/backend/src/test/java/reviewme/fixture/QuestionFixture.java
@@ -1,10 +1,23 @@
package reviewme.fixture;
-import reviewme.question.domain.Question;
-import reviewme.question.domain.QuestionType;
+import java.util.List;
+import java.util.stream.IntStream;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.OptionType;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
public class QuestionFixture {
+ public static Question 선택형_질문(boolean required, int optionCount, int position) {
+ List optionItems = IntStream.rangeClosed(1, optionCount)
+ .mapToObj(i -> new OptionItem("선택지 본문", i, OptionType.CATEGORY))
+ .toList();
+ OptionGroup optionGroup = new OptionGroup(optionItems, 1, optionItems.size());
+ return new Question(required, QuestionType.CHECKBOX, optionGroup, "본문", null, position);
+ }
+
public static Question 선택형_필수_질문() {
return 선택형_필수_질문(1);
}
@@ -13,14 +26,6 @@ public class QuestionFixture {
return new Question(true, QuestionType.CHECKBOX, "본문", null, position);
}
- public static Question 선택형_옵션_질문() {
- return 선택형_옵션_질문(1);
- }
-
- public static Question 선택형_옵션_질문(int position) {
- return new Question(false, QuestionType.CHECKBOX, "본문", null, position);
- }
-
public static Question 서술형_필수_질문() {
return 서술형_필수_질문(1);
}
diff --git a/backend/src/test/java/reviewme/fixture/ReviewGroupFixture.java b/backend/src/test/java/reviewme/fixture/ReviewGroupFixture.java
index caf03a6ef..d134bf3d0 100644
--- a/backend/src/test/java/reviewme/fixture/ReviewGroupFixture.java
+++ b/backend/src/test/java/reviewme/fixture/ReviewGroupFixture.java
@@ -4,11 +4,15 @@
public class ReviewGroupFixture {
- public static ReviewGroup 리뷰_그룹() {
- return 리뷰_그룹("reviewRequestCode", "groupAccessCode");
- }
-
public static ReviewGroup 리뷰_그룹(String reviewRequestCode, String groupAccessCode) {
return new ReviewGroup("revieweeName", "projectName", reviewRequestCode, groupAccessCode, 1L);
}
+
+ public static ReviewGroup 템플릿_지정_리뷰_그룹(long templateId) {
+ return new ReviewGroup("reviewee", "project", "requestCode", "accessCode", templateId);
+ }
+
+ public static ReviewGroup 리뷰_그룹() {
+ return 리뷰_그룹("reviewRequestCode", "groupAccessCode");
+ }
}
diff --git a/backend/src/test/java/reviewme/fixture/SectionFixture.java b/backend/src/test/java/reviewme/fixture/SectionFixture.java
index ce8d5d906..3c7cc44e3 100644
--- a/backend/src/test/java/reviewme/fixture/SectionFixture.java
+++ b/backend/src/test/java/reviewme/fixture/SectionFixture.java
@@ -1,24 +1,26 @@
package reviewme.fixture;
import java.util.List;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
import reviewme.template.domain.Section;
import reviewme.template.domain.VisibleType;
public class SectionFixture {
- public static Section 항상_보이는_섹션(List questionIds) {
- return 항상_보이는_섹션(questionIds, 1);
+ public static Section 항상_보이는_섹션(List questions) {
+ return 항상_보이는_섹션(questions, 1);
}
- public static Section 항상_보이는_섹션(List questionIds, int position) {
- return new Section(VisibleType.ALWAYS, questionIds, null, "섹션명", "머릿말", position);
+ public static Section 항상_보이는_섹션(List questions, int position) {
+ return new Section(VisibleType.ALWAYS, questions, null, "섹션명", "머릿말", position);
}
- public static Section 조건부로_보이는_섹션(List questionIds, long onSelectedOptionId) {
- return 조건부로_보이는_섹션(questionIds, onSelectedOptionId, 1);
+ public static Section 조건부로_보이는_섹션(List questions, OptionItem onSelectedOption) {
+ return 조건부로_보이는_섹션(questions, onSelectedOption, 1);
}
- public static Section 조건부로_보이는_섹션(List questionIds, long onSelectedOptionId, int position) {
- return new Section(VisibleType.CONDITIONAL, questionIds, onSelectedOptionId, "섹션명", "머릿말", position);
+ public static Section 조건부로_보이는_섹션(List questions, OptionItem onSelectedOption, int position) {
+ return new Section(VisibleType.CONDITIONAL, questions, onSelectedOption, "섹션명", "머릿말", position);
}
}
diff --git a/backend/src/test/java/reviewme/fixture/TemplateFixture.java b/backend/src/test/java/reviewme/fixture/TemplateFixture.java
deleted file mode 100644
index 44826daee..000000000
--- a/backend/src/test/java/reviewme/fixture/TemplateFixture.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package reviewme.fixture;
-
-import java.util.List;
-import reviewme.template.domain.Template;
-
-public class TemplateFixture {
-
- public static Template 템플릿(List sectionIds) {
- return new Template(sectionIds);
- }
-}
diff --git a/backend/src/test/java/reviewme/global/RequestLimitInterceptorTest.java b/backend/src/test/java/reviewme/global/RequestLimitInterceptorTest.java
deleted file mode 100644
index 998639691..000000000
--- a/backend/src/test/java/reviewme/global/RequestLimitInterceptorTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package reviewme.global;
-
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.springframework.http.HttpHeaders.USER_AGENT;
-
-import jakarta.servlet.http.HttpServletRequest;
-import java.time.Duration;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.ValueOperations;
-import reviewme.config.RequestLimitProperties;
-import reviewme.global.exception.TooManyRequestException;
-
-class RequestLimitInterceptorTest {
-
- private final HttpServletRequest request = mock(HttpServletRequest.class);
- private final RedisTemplate redisTemplate = mock(RedisTemplate.class);
- private final ValueOperations valueOperations = mock(ValueOperations.class);
- private final RequestLimitProperties requestLimitProperties = mock(RequestLimitProperties.class);
- private final RequestLimitInterceptor interceptor = new RequestLimitInterceptor(redisTemplate, requestLimitProperties);
- private final String requestKey = "RequestURI: /api/v2/reviews, RemoteAddr: localhost, UserAgent: Postman";
-
- @BeforeEach
- void setUp() {
- given(request.getMethod()).willReturn("POST");
- given(request.getRequestURI()).willReturn("/api/v2/reviews");
- given(request.getRemoteAddr()).willReturn("localhost");
- given(request.getHeader(USER_AGENT)).willReturn("Postman");
-
- given(redisTemplate.opsForValue()).willReturn(valueOperations);
- given(requestLimitProperties.duration()).willReturn(Duration.ofSeconds(1));
- given(requestLimitProperties.threshold()).willReturn(3L);
- }
-
- @Test
- void POST_요청이_아니면_통과한다() {
- // given
- HttpServletRequest request = mock(HttpServletRequest.class);
- given(request.getMethod()).willReturn("GET");
-
- // when
- boolean result = interceptor.preHandle(request, null, null);
-
- // then
- assertThat(result).isTrue();
- }
-
- @Test
- void 특정_POST_요청이_처음이_아니며_최대_빈도보다_작을_경우_빈도를_1증가시킨다() {
- // given
- long requestCount = 1;
- given(valueOperations.get(anyString())).willReturn(requestCount);
-
- // when
- boolean result = interceptor.preHandle(request, null, null);
-
- // then
- assertThat(result).isTrue();
- verify(valueOperations).increment(requestKey);
- }
-
- @Test
- void 특정_POST_요청이_처음이_아니며_최대_빈도보다_클_경우_예외를_발생시킨다() {
- // given
- long maxRequestCount = 3;
- given(valueOperations.increment(anyString())).willReturn(maxRequestCount + 1);
-
- // when & then
- assertThatThrownBy(() -> interceptor.preHandle(request, null, null))
- .isInstanceOf(TooManyRequestException.class);
- }
-}
diff --git a/backend/src/test/java/reviewme/highlight/domain/HighlightedLinesTest.java b/backend/src/test/java/reviewme/highlight/domain/HighlightedLinesTest.java
index 53d81c209..d3e4a443c 100644
--- a/backend/src/test/java/reviewme/highlight/domain/HighlightedLinesTest.java
+++ b/backend/src/test/java/reviewme/highlight/domain/HighlightedLinesTest.java
@@ -84,4 +84,21 @@ class HighlightedLinesTest {
assertThatCode(() -> highlightedLines.addRange(invalidLineIndex, 0, 1))
.isInstanceOf(InvalidHighlightLineIndexException.class);
}
+
+ @Test
+ void 하이라이트가_존재하는_부분만_엔티티로_변환한다() {
+ // given
+ HighlightedLines lines = new HighlightedLines("0\n11\n222");
+ lines.addRange(0, 0, 0);
+ lines.addRange(2, 2, 2);
+
+ // when
+ List highlights = lines.toHighlights(1L);
+
+ // then
+ assertThat(highlights).containsExactly(
+ new Highlight(1L, 0, new HighlightRange(0, 0)),
+ new Highlight(1L, 2, new HighlightRange(2, 2))
+ );
+ }
}
diff --git a/backend/src/test/java/reviewme/highlight/repository/HighlightRepositoryTest.java b/backend/src/test/java/reviewme/highlight/repository/HighlightRepositoryTest.java
index 40b584f47..528ccbfff 100644
--- a/backend/src/test/java/reviewme/highlight/repository/HighlightRepositoryTest.java
+++ b/backend/src/test/java/reviewme/highlight/repository/HighlightRepositoryTest.java
@@ -2,13 +2,21 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
+import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
+import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import reviewme.highlight.domain.Highlight;
import reviewme.highlight.domain.HighlightRange;
+import reviewme.review.domain.Answer;
+import reviewme.review.domain.Review;
+import reviewme.review.domain.TextAnswer;
+import reviewme.review.repository.ReviewRepository;
+import reviewme.reviewgroup.domain.ReviewGroup;
+import reviewme.reviewgroup.repository.ReviewGroupRepository;
@DataJpaTest
class HighlightRepositoryTest {
@@ -16,6 +24,30 @@ class HighlightRepositoryTest {
@Autowired
private HighlightRepository highlightRepository;
+ @Autowired
+ private ReviewRepository reviewRepository;
+
+ @Autowired
+ private ReviewGroupRepository reviewGroupRepository;
+
+ @Test
+ void 한_번에_여러_하이라이트를_벌크_삽입한다() {
+ // given
+ List highlights = List.of(
+ new Highlight(1L, 1, new HighlightRange(1, 2)),
+ new Highlight(1L, 1, new HighlightRange(3, 5))
+ );
+
+ // when
+ highlightRepository.saveAll(highlights);
+
+ // then
+ List actual = highlightRepository.findAllByAnswerIdsOrderedAsc(List.of(1L));
+ assertThat(actual)
+ .extracting(Highlight::getHighlightRange)
+ .containsExactly(new HighlightRange(1, 2), new HighlightRange(3, 5));
+ }
+
@Test
void 하이라이트를_줄번호_시작_인덱스_순서대로_정렬해서_가져온다() {
// given
@@ -44,4 +76,43 @@ class HighlightRepositoryTest {
.containsExactly(1, 4, 2, 6, 3)
);
}
+
+ @Test
+ void 그룹_아이디와_질문_아이디로_하이라이트를_삭제한다() {
+ // given
+ ReviewGroup reviewGroup1 = reviewGroupRepository.save(리뷰_그룹());
+ ReviewGroup reviewGroup2 = reviewGroupRepository.save(리뷰_그룹());
+
+ List answers1 = List.of(
+ new TextAnswer(1L, "A1"),
+ new TextAnswer(2L, "A2"),
+ new TextAnswer(3L, "A3")
+ );
+ List answers2 = List.of(
+ new TextAnswer(1L, "B1"),
+ new TextAnswer(2L, "B2"),
+ new TextAnswer(3L, "B3")
+ );
+ reviewRepository.save(new Review(1L, reviewGroup1.getId(), answers1));
+ reviewRepository.save(new Review(2L, reviewGroup2.getId(), answers2));
+
+ List answerIds = new ArrayList<>();
+ answerIds.addAll(answers1.stream().map(Answer::getId).toList());
+ answerIds.addAll(answers2.stream().map(Answer::getId).toList());
+
+ HighlightRange range = new HighlightRange(0, 1);
+ answerIds.stream()
+ .map(answerId -> new Highlight(answerId, 0, range))
+ .forEach(highlightRepository::save);
+
+ // when
+ highlightRepository.deleteByReviewGroupIdAndQuestionId(reviewGroup1.getId(), 1L);
+
+ // then
+ List actual = highlightRepository.findAllByAnswerIdsOrderedAsc(answerIds);
+ assertAll(
+ () -> assertThat(actual).hasSize(5),
+ () -> assertThat(actual).extracting(Highlight::getAnswerId).doesNotContain(answers1.get(0).getId())
+ );
+ }
}
diff --git a/backend/src/test/java/reviewme/highlight/service/HighlightServiceTest.java b/backend/src/test/java/reviewme/highlight/service/HighlightServiceTest.java
index 32eed36b8..0a3ff0c4e 100644
--- a/backend/src/test/java/reviewme/highlight/service/HighlightServiceTest.java
+++ b/backend/src/test/java/reviewme/highlight/service/HighlightServiceTest.java
@@ -5,7 +5,6 @@
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -17,14 +16,15 @@
import reviewme.highlight.service.dto.HighlightRequest;
import reviewme.highlight.service.dto.HighlightedLineRequest;
import reviewme.highlight.service.dto.HighlightsRequest;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
import reviewme.review.repository.ReviewRepository;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
-import reviewme.template.repository.SectionRepository;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -42,33 +42,26 @@ class HighlightServiceTest {
@Autowired
private ReviewRepository reviewRepository;
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
@Autowired
private TemplateRepository templateRepository;
@Test
void 하이라이트_반영을_요청하면_리뷰_그룹과_질문에_해당하는_기존_하이라이트를_모두_삭제한다() {
// given
- long questionId = questionRepository.save(서술형_필수_질문()).getId();
- long sectionId = sectionRepository.save(항상_보이는_섹션(List.of(questionId))).getId();
- long templateId = templateRepository.save(템플릿(List.of(sectionId))).getId();
- String reviewRequestCode = "reviewRequestCode";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, "groupAccessCode"));
-
- TextAnswer textAnswer1 = new TextAnswer(questionId, "text answer1");
- TextAnswer textAnswer2 = new TextAnswer(questionId, "text answer2");
- Review review = reviewRepository.save(new Review(templateId, reviewGroup.getId(), List.of(textAnswer1, textAnswer2)));
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+
+ TextAnswer textAnswer1 = new TextAnswer(question.getId(), "text answer1");
+ TextAnswer textAnswer2 = new TextAnswer(question.getId(), "text answer2");
+ reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer1, textAnswer2)));
Highlight highlight = highlightRepository.save(new Highlight(textAnswer1.getId(), 1, new HighlightRange(1, 1)));
HighlightIndexRangeRequest indexRangeRequest = new HighlightIndexRangeRequest(1, 1);
HighlightedLineRequest lineRequest = new HighlightedLineRequest(0, List.of(indexRangeRequest));
HighlightRequest highlightRequest1 = new HighlightRequest(textAnswer2.getId(), List.of(lineRequest));
- HighlightsRequest highlightsRequest = new HighlightsRequest(questionId, List.of(highlightRequest1));
+ HighlightsRequest highlightsRequest = new HighlightsRequest(question.getId(), List.of(highlightRequest1));
// when
highlightService.editHighlight(highlightsRequest, reviewGroup);
@@ -80,15 +73,13 @@ class HighlightServiceTest {
@Test
void 하이라이트_반영을_요청하면_새로운_하이라이트가_저장된다() {
// given
- long questionId = questionRepository.save(서술형_필수_질문()).getId();
- long sectionId = sectionRepository.save(항상_보이는_섹션(List.of(questionId))).getId();
- long templateId = templateRepository.save(템플릿(List.of(sectionId))).getId();
- String reviewRequestCode = "reviewRequestCode";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, "groupAccessCode"));
-
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- TextAnswer textAnswer = new TextAnswer(questionId, "text answer1");
- Review review = reviewRepository.save(new Review(templateId, reviewGroup.getId(), List.of(textAnswer)));
+ TextAnswer textAnswer = new TextAnswer(question.getId(), "text answer1");
+ reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer)));
highlightRepository.save(new Highlight(1, 1, new HighlightRange(1, 1)));
int startIndex = 2;
@@ -96,34 +87,33 @@ class HighlightServiceTest {
HighlightIndexRangeRequest indexRangeRequest = new HighlightIndexRangeRequest(startIndex, endIndex);
HighlightedLineRequest lineRequest = new HighlightedLineRequest(0, List.of(indexRangeRequest));
HighlightRequest highlightRequest = new HighlightRequest(textAnswer.getId(), List.of(lineRequest));
- HighlightsRequest highlightsRequest = new HighlightsRequest(questionId, List.of(highlightRequest));
+ HighlightsRequest highlightsRequest = new HighlightsRequest(question.getId(), List.of(highlightRequest));
// when
highlightService.editHighlight(highlightsRequest, reviewGroup);
// then
- List highlights = highlightRepository.findAll();
+ List highlights = highlightRepository.findAllByAnswerIdsOrderedAsc(List.of(textAnswer.getId()));
+ Highlight actual = highlights.get(0);
assertAll(
- () -> assertThat(highlights.get(0).getAnswerId()).isEqualTo(textAnswer.getId()),
- () -> assertThat(highlights.get(0).getHighlightRange()).isEqualTo(
- new HighlightRange(startIndex, endIndex))
+ () -> assertThat(actual.getAnswerId()).isEqualTo(textAnswer.getId()),
+ () -> assertThat(actual.getHighlightRange()).isEqualTo(new HighlightRange(startIndex, endIndex))
);
}
@Test
void 하이라이트_할_내용이_없는_요청이_오면_기존에_있던_내용을_삭제하고_아무것도_저장하지_않는다() {
// given
- long questionId = questionRepository.save(서술형_필수_질문()).getId();
- long sectionId = sectionRepository.save(항상_보이는_섹션(List.of(questionId))).getId();
- long templateId = templateRepository.save(템플릿(List.of(sectionId))).getId();
- String reviewRequestCode = "reviewRequestCode";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, "groupAccessCode"));
-
- TextAnswer textAnswer = new TextAnswer(questionId, "text answer1");
- Review review = reviewRepository.save(new Review(templateId, reviewGroup.getId(), List.of(textAnswer)));
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+
+ TextAnswer textAnswer = new TextAnswer(question.getId(), "text answer1");
+ reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer)));
Highlight highlight = highlightRepository.save(new Highlight(textAnswer.getId(), 1, new HighlightRange(1, 1)));
- HighlightsRequest highlightsRequest = new HighlightsRequest(questionId, List.of());
+ HighlightsRequest highlightsRequest = new HighlightsRequest(question.getId(), List.of());
// when
highlightService.editHighlight(highlightsRequest, reviewGroup);
diff --git a/backend/src/test/java/reviewme/highlight/service/mapper/HighlightMapperTest.java b/backend/src/test/java/reviewme/highlight/service/mapper/HighlightMapperTest.java
index 14a6639f9..a8954cb1f 100644
--- a/backend/src/test/java/reviewme/highlight/service/mapper/HighlightMapperTest.java
+++ b/backend/src/test/java/reviewme/highlight/service/mapper/HighlightMapperTest.java
@@ -5,7 +5,6 @@
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -17,13 +16,14 @@
import reviewme.highlight.service.dto.HighlightRequest;
import reviewme.highlight.service.dto.HighlightedLineRequest;
import reviewme.highlight.service.dto.HighlightsRequest;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
import reviewme.review.repository.ReviewRepository;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
-import reviewme.template.repository.SectionRepository;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -41,28 +41,20 @@ class HighlightMapperTest {
@Autowired
private ReviewRepository reviewRepository;
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
@Autowired
private TemplateRepository templateRepository;
@Test
void 하이라이트_요청과_기존_서술형_답변으로_하이라이트를_매핑한다() {
// given
- long questionId = questionRepository.save(서술형_필수_질문()).getId();
- long sectionId = sectionRepository.save(항상_보이는_섹션(List.of(questionId))).getId();
- long templateId = templateRepository.save(템플릿(List.of(sectionId))).getId();
- String reviewRequestCode = "reviewRequestCode";
- long reviewGroupId = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, "groupAccessCode"))
- .getId();
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
+ long reviewGroupId = reviewGroupRepository.save(리뷰_그룹()).getId();
- TextAnswer textAnswer1 = new TextAnswer(questionId, "text answer1");
- TextAnswer textAnswer2 = new TextAnswer(questionId, "text answer2");
- Review review = reviewRepository.save(new Review(templateId, reviewGroupId, List.of(textAnswer1, textAnswer2)));
+ TextAnswer textAnswer1 = new TextAnswer(question.getId(), "text answer1");
+ TextAnswer textAnswer2 = new TextAnswer(question.getId(), "text answer2");
+ reviewRepository.save(new Review(template.getId(), reviewGroupId, List.of(textAnswer1, textAnswer2)));
highlightRepository.save(new Highlight(1, 1, new HighlightRange(1, 1)));
@@ -74,8 +66,10 @@ class HighlightMapperTest {
HighlightedLineRequest lineRequest2 = new HighlightedLineRequest(lineIndex, List.of(rangeRequest));
HighlightRequest highlightRequest1 = new HighlightRequest(textAnswer1.getId(), List.of(lineRequest1));
HighlightRequest highlightRequest2 = new HighlightRequest(textAnswer2.getId(), List.of(lineRequest2));
- HighlightsRequest highlightsRequest = new HighlightsRequest(questionId,
- List.of(highlightRequest1, highlightRequest2));
+ HighlightsRequest highlightsRequest = new HighlightsRequest(
+ question.getId(),
+ List.of(highlightRequest1, highlightRequest2)
+ );
// when
List highlights = highlightMapper.mapToHighlights(highlightsRequest);
diff --git a/backend/src/test/java/reviewme/highlight/service/validator/HighlightValidatorTest.java b/backend/src/test/java/reviewme/highlight/service/validator/HighlightValidatorTest.java
deleted file mode 100644
index 84bf793d2..000000000
--- a/backend/src/test/java/reviewme/highlight/service/validator/HighlightValidatorTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package reviewme.highlight.service.validator;
-
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
-import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
-
-import java.util.List;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.highlight.service.dto.HighlightRequest;
-import reviewme.highlight.service.dto.HighlightsRequest;
-import reviewme.highlight.service.exception.SubmittedAnswerAndProvidedAnswerMismatchException;
-import reviewme.question.repository.QuestionRepository;
-import reviewme.review.domain.Review;
-import reviewme.review.domain.TextAnswer;
-import reviewme.review.repository.ReviewRepository;
-import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.reviewgroup.repository.ReviewGroupRepository;
-import reviewme.support.ServiceTest;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
-
-@ServiceTest
-class HighlightValidatorTest {
-
- @Autowired
- private HighlightValidator highlightValidator;
-
- @Autowired
- private ReviewGroupRepository reviewGroupRepository;
-
- @Autowired
- private ReviewRepository reviewRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private TemplateRepository templateRepository;
-
- @Test
- void 하이라이트의_답변_id가_하이라이트의_질문_id에_해당하는_답변이_아니면_예외를_발생한다() {
- // given
- long questionId1 = questionRepository.save(서술형_필수_질문()).getId();
- long questionId2 = questionRepository.save(서술형_필수_질문()).getId();
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(questionId1, questionId2)));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- TextAnswer textAnswer_Q1 = new TextAnswer(questionId1, "text answer 1");
-
- HighlightRequest highlightRequest = new HighlightRequest(textAnswer_Q1.getId(), List.of());
- HighlightsRequest highlightsRequest = new HighlightsRequest(questionId2, List.of(highlightRequest));
-
- // when && then
- assertThatCode(() -> highlightValidator.validate(highlightsRequest, reviewGroup))
- .isInstanceOf(SubmittedAnswerAndProvidedAnswerMismatchException.class);
- }
-
- @Test
- void 하이라이트의_답변_id가_리뷰_그룹에_달린_답변이_아니면_예외를_발생한다() {
- // given
- long questionId = questionRepository.save(서술형_필수_질문()).getId();
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(questionId)));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewGroup reviewGroup1 = reviewGroupRepository.save(리뷰_그룹());
- ReviewGroup reviewGroup2 = reviewGroupRepository.save(리뷰_그룹());
- TextAnswer textAnswer1 = new TextAnswer(questionId, "text answer1");
- TextAnswer textAnswer2 = new TextAnswer(questionId, "text answer2");
- reviewRepository.saveAll(List.of(
- new Review(template.getId(), reviewGroup1.getId(), List.of(textAnswer1)),
- new Review(template.getId(), reviewGroup2.getId(), List.of(textAnswer2))
- ));
-
- HighlightRequest highlightRequest = new HighlightRequest(textAnswer2.getId(), List.of());
- HighlightsRequest highlightsRequest = new HighlightsRequest(1L, List.of(highlightRequest));
-
- // when && then
- assertThatCode(() -> highlightValidator.validate(highlightsRequest, reviewGroup1))
- .isInstanceOf(SubmittedAnswerAndProvidedAnswerMismatchException.class);
- }
-
- @Test
- void 하이라이트의_질문_id가_리뷰_그룹의_템플릿에_속한_질문이_아니면_예외를_발생한다() {
- // given
- long questionId1 = questionRepository.save(서술형_필수_질문()).getId();
- long questionId2 = questionRepository.save(서술형_필수_질문()).getId();
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(questionId1)));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- TextAnswer textAnswer_Q1 = new TextAnswer(questionId1, "text answer 1");
-
- HighlightRequest highlightRequest = new HighlightRequest(textAnswer_Q1.getId(), List.of());
- HighlightsRequest highlightsRequest = new HighlightsRequest(questionId2, List.of(highlightRequest));
-
- // when && then
- assertThatCode(() -> highlightValidator.validate(highlightsRequest, reviewGroup))
- .isInstanceOf(SubmittedAnswerAndProvidedAnswerMismatchException.class);
- }
-}
diff --git a/backend/src/test/java/reviewme/question/repository/OptionGroupRepositoryTest.java b/backend/src/test/java/reviewme/question/repository/OptionGroupRepositoryTest.java
deleted file mode 100644
index 1bc3ea107..000000000
--- a/backend/src/test/java/reviewme/question/repository/OptionGroupRepositoryTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package reviewme.question.repository;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.*;
-import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
-
-import java.util.List;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.Question;
-
-@DataJpaTest
-class OptionGroupRepositoryTest {
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Test
- void 질문_아이디_그룹에_포함되는_모든_옵션_그룹을_불러온다() {
- // given
- Question question1 = questionRepository.save(선택형_필수_질문());
- Question question2 = questionRepository.save(선택형_필수_질문());
- Question question3 = questionRepository.save(선택형_필수_질문());
-
- OptionGroup optionGroup1 = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionGroup optionGroup2 = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionGroup optionGroup3 = optionGroupRepository.save(선택지_그룹(question2.getId()));
- OptionGroup optionGroup4 = optionGroupRepository.save(선택지_그룹(question2.getId()));
- OptionGroup optionGroup5 = optionGroupRepository.save(선택지_그룹(question3.getId()));
- OptionGroup optionGroup6 = optionGroupRepository.save(선택지_그룹(question3.getId()));
-
-
- // when
- List actual = optionGroupRepository.findAllByQuestionIds(
- List.of(question1.getId(), question2.getId()));
-
- // then
- assertThat(actual).containsExactlyInAnyOrder(optionGroup1, optionGroup2, optionGroup3, optionGroup4);
- }
-}
diff --git a/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java b/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java
deleted file mode 100644
index 5aebbf06b..000000000
--- a/backend/src/test/java/reviewme/question/repository/OptionItemRepositoryTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package reviewme.question.repository;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.OptionItemFixture.선택지;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
-
-import java.util.List;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.OptionType;
-import reviewme.question.domain.Question;
-
-@DataJpaTest
-class OptionItemRepositoryTest {
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Test
- void 옵션_타입에_해당하는_모든_옵션_아이템을_불러온다() {
- // given
- Question question = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question.getId()));
-
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup.getId()));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup.getId()));
-
- // when
- List actual = optionItemRepository.findAllByOptionType(OptionType.CATEGORY);
-
- // then
- assertThat(actual).containsExactlyInAnyOrder(optionItem1, optionItem2);
- }
-
- @Test
- void 질문_아이디_그룹에_포함되는_모든_옵션_아이템을_불러온다() {
- // given
- Question question1 = questionRepository.save(선택형_필수_질문());
- Question question2 = questionRepository.save(선택형_필수_질문());
- Question question3 = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup1 = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionGroup optionGroup2 = optionGroupRepository.save(선택지_그룹(question2.getId()));
- OptionGroup optionGroup3 = optionGroupRepository.save(선택지_그룹(question3.getId()));
-
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup1.getId()));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup1.getId()));
- OptionItem optionItem3 = optionItemRepository.save(선택지(optionGroup2.getId()));
- OptionItem optionItem4 = optionItemRepository.save(선택지(optionGroup3.getId()));
-
- // when
- List actual = optionItemRepository.findAllByQuestionIds(
- List.of(question1.getId(), question2.getId()));
-
- // then
- assertThat(actual).containsExactlyInAnyOrder(optionItem1, optionItem2, optionItem3);
- }
-}
diff --git a/backend/src/test/java/reviewme/question/repository/QuestionRepositoryTest.java b/backend/src/test/java/reviewme/question/repository/QuestionRepositoryTest.java
deleted file mode 100644
index da694e335..000000000
--- a/backend/src/test/java/reviewme/question/repository/QuestionRepositoryTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package reviewme.question.repository;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.OptionItemFixture.선택지;
-import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
-import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
-
-import java.util.List;
-import java.util.Set;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.reviewgroup.repository.ReviewGroupRepository;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
-
-@DataJpaTest
-class QuestionRepositoryTest {
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private TemplateRepository templateRepository;
-
- @Autowired
- private ReviewGroupRepository reviewGroupRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
- @Test
- void 템플릿_아이디로_질문_목록_아이디를_모두_가져온다() {
- // given
- Question question1 = questionRepository.save(서술형_필수_질문(1));
- Question question2 = questionRepository.save(서술형_필수_질문(2));
- Question question3 = questionRepository.save(서술형_필수_질문(1));
- Question question4 = questionRepository.save(서술형_필수_질문(2));
-
- List sectionQuestion1 = List.of(question1.getId(), question2.getId());
- List sectionQuestion2 = List.of(question3.getId(), question4.getId());
- Section section1 = sectionRepository.save(항상_보이는_섹션(sectionQuestion1));
- sectionRepository.save(항상_보이는_섹션(sectionQuestion2));
- List sectionIds = List.of(section1.getId());
- Template template = templateRepository.save(템플릿(sectionIds));
-
- // when
- Set actual = questionRepository.findAllQuestionIdByTemplateId(template.getId());
-
- // then
- assertThat(actual).containsExactlyInAnyOrder(question1.getId(), question2.getId());
- }
-
- @Test
- void 템플릿_아이디로_질문_목록을_모두_가져온다() {
- // given
- Question question1 = questionRepository.save(서술형_필수_질문(1));
- Question question2 = questionRepository.save(서술형_필수_질문(2));
- Question question3 = questionRepository.save(서술형_필수_질문(1));
- Question question4 = questionRepository.save(서술형_필수_질문(2));
-
- List sectionQuestion1 = List.of(question1.getId(), question2.getId());
- List sectionQuestion2 = List.of(question3.getId(), question4.getId());
- Section section1 = sectionRepository.save(항상_보이는_섹션(sectionQuestion1));
- sectionRepository.save(항상_보이는_섹션(sectionQuestion2));
- List sectionIds = List.of(section1.getId());
- Template template = templateRepository.save(템플릿(sectionIds));
-
- // when
- List actual = questionRepository.findAllByTemplatedId(template.getId());
-
- // then
- assertThat(actual).containsExactlyInAnyOrder(question1, question2);
- }
-
- @Test
- void 섹션_아이디에_해당하는_질문을_순서대로_가져온다() {
- // given
- Question question1 = questionRepository.save(서술형_필수_질문(1));
- Question question2 = questionRepository.save(서술형_필수_질문(2));
- Question question3 = questionRepository.save(서술형_필수_질문(3));
- Question question4 = questionRepository.save(서술형_필수_질문(1));
-
- List sectionQuestion1 = List.of(question1.getId(), question2.getId(), question3.getId());
- List sectionQuestion2 = List.of(question4.getId());
- Section section1 = sectionRepository.save(항상_보이는_섹션(sectionQuestion1));
- Section section2 = sectionRepository.save(항상_보이는_섹션(sectionQuestion2));
- Template template = templateRepository.save(템플릿(List.of(section1.getId(), section2.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(new ReviewGroup(
- "reviewee", "projectName", "reviewRequestCode", "groupAccessCode", template.getId()
- ));
-
- // when
- List questionsInSection = questionRepository.findAllBySectionIdOrderByPosition(section1.getId());
-
- // then
- assertThat(questionsInSection).containsExactly(question1, question2, question3);
- }
-
- @Test
- void 질문_아이디에_해당하는_모든_옵션_아이템을_순서대로_불러온다() {
- // given
- Question question1 = questionRepository.save(선택형_필수_질문());
- Question question2 = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup1 = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionGroup optionGroup2 = optionGroupRepository.save(선택지_그룹(question2.getId()));
-
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup1.getId()));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup1.getId()));
- OptionItem optionItem3 = optionItemRepository.save(선택지(optionGroup2.getId()));
-
- // when
- List optionItemsForQuestion1
- = questionRepository.findAllOptionItemsByIdOrderByPosition(question1.getId());
-
- // then
- assertThat(optionItemsForQuestion1).containsExactly(optionItem1, optionItem2);
- }
-}
diff --git a/backend/src/test/java/reviewme/review/repository/AnswerRepositoryTest.java b/backend/src/test/java/reviewme/review/repository/AnswerRepositoryTest.java
index e13ce1427..db0873650 100644
--- a/backend/src/test/java/reviewme/review/repository/AnswerRepositoryTest.java
+++ b/backend/src/test/java/reviewme/review/repository/AnswerRepositoryTest.java
@@ -1,27 +1,15 @@
package reviewme.review.repository;
import static org.assertj.core.api.Assertions.assertThat;
-import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
-import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Answer;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
-import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.reviewgroup.repository.ReviewGroupRepository;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
@DataJpaTest
class AnswerRepositoryTest {
@@ -29,43 +17,27 @@ class AnswerRepositoryTest {
@Autowired
private AnswerRepository answerRepository;
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private TemplateRepository templateRepository;
-
- @Autowired
- private ReviewGroupRepository reviewGroupRepository;
-
@Autowired
private ReviewRepository reviewRepository;
@Test
void 내가_받은_답변들_중_주어진_질문들에_대한_답변들을_최신_작성순으로_제한된_수만_반환한다() {
// given
- Question question1 = questionRepository.save(서술형_필수_질문());
- Question question2 = questionRepository.save(서술형_필수_질문());
- Question question3 = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(
- List.of(question1.getId(), question2.getId(), question3.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- TextAnswer answer1 = new TextAnswer(question1.getId(), "답1".repeat(20));
- TextAnswer answer2 = new TextAnswer(question2.getId(), "답2".repeat(20));
- TextAnswer answer3 = new TextAnswer(question2.getId(), "답3".repeat(20));
- TextAnswer answer4 = new TextAnswer(question3.getId(), "답4".repeat(20));
- reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answer1)));
- reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answer2)));
- reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answer3)));
+ long reviewGroupId = 1L;
+ long templateId = 1L;
+
+ TextAnswer answer1 = new TextAnswer(1L, "답1".repeat(20));
+ TextAnswer answer2 = new TextAnswer(2L, "답2".repeat(20));
+ TextAnswer answer3 = new TextAnswer(2L, "답3".repeat(20));
+ TextAnswer answer4 = new TextAnswer(3L, "답4".repeat(20));
+ reviewRepository.save(new Review(templateId, reviewGroupId, List.of(answer1)));
+ reviewRepository.save(new Review(templateId, reviewGroupId, List.of(answer2)));
+ reviewRepository.save(new Review(templateId, reviewGroupId, List.of(answer3)));
// when
List actual = answerRepository.findReceivedAnswersByQuestionIds(
- reviewGroup.getId(), List.of(question1.getId(), question2.getId()), 2);
+ reviewGroupId, List.of(1L, 2L), 2
+ );
// then
assertThat(actual).containsExactly(answer3, answer2);
@@ -74,13 +46,13 @@ class AnswerRepositoryTest {
@Test
void 리뷰_그룹_id로_리뷰들을_찾아_id를_반환한다() {
// given
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ long reviewGroupId = 1L;
TextAnswer answer1 = new TextAnswer(1L, "text answer1");
TextAnswer answer2 = new TextAnswer(1L, "text answer2");
- Review review = reviewRepository.save(new Review(1L, reviewGroup.getId(), List.of(answer1, answer2)));
+ Review review = reviewRepository.save(new Review(1L, reviewGroupId, List.of(answer1, answer2)));
// when
- Set actual = answerRepository.findIdsByReviewGroupId(reviewGroup.getId());
+ Set actual = answerRepository.findIdsByReviewGroupId(reviewGroupId);
// then
assertThat(actual).containsExactly(answer1.getId(), answer2.getId());
@@ -89,20 +61,18 @@ class AnswerRepositoryTest {
@Test
void 질문_id로_리뷰들을_찾아_id를_반환한다() {
// given
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- long questionId1 = questionRepository.save(서술형_필수_질문()).getId();
- long questionId2 = questionRepository.save(서술형_필수_질문()).getId();
- TextAnswer textAnswer1_Q1 = new TextAnswer(questionId1, "text answer1 by Q1");
- TextAnswer textAnswer2_Q1 = new TextAnswer(questionId1, "text answer2 by Q1");
- TextAnswer textAnswer1_Q2 = new TextAnswer(questionId2, "text answer1 by Q2");
+ long reviewGroupId = 1L;
+ TextAnswer textAnswer1_Q1 = new TextAnswer(1L, "text answer1 by Q1");
+ TextAnswer textAnswer2_Q1 = new TextAnswer(1L, "text answer2 by Q1");
+ TextAnswer textAnswer1_Q2 = new TextAnswer(2L, "text answer1 by Q2");
reviewRepository.saveAll(List.of(
- new Review(1L, reviewGroup.getId(), List.of(textAnswer1_Q1, textAnswer2_Q1)),
- new Review(1L, reviewGroup.getId(), List.of(textAnswer1_Q2)
+ new Review(1L, reviewGroupId, List.of(textAnswer1_Q1, textAnswer2_Q1)),
+ new Review(1L, reviewGroupId, List.of(textAnswer1_Q2)
)));
// when
- Set actual = answerRepository.findIdsByQuestionId(questionId1);
+ Set actual = answerRepository.findIdsByQuestionId(1L);
// then
assertThat(actual).containsExactly(textAnswer1_Q1.getId(), textAnswer2_Q1.getId());
diff --git a/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java b/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java
index 2149c7ed9..3a2786702 100644
--- a/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java
+++ b/backend/src/test/java/reviewme/review/repository/ReviewRepositoryTest.java
@@ -4,7 +4,6 @@
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.time.LocalDate;
import java.util.List;
@@ -13,14 +12,12 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Review;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
+import reviewme.template.domain.Question;
import reviewme.template.domain.Section;
import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
import reviewme.template.repository.TemplateRepository;
@DataJpaTest
@@ -32,22 +29,15 @@ class ReviewRepositoryTest {
@Autowired
private ReviewGroupRepository reviewGroupRepository;
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
@Autowired
private TemplateRepository templateRepository;
@Test
void 리뷰_그룹_아이디에_해당하는_모든_리뷰를_생성일_기준_내림차순으로_불러온다() {
// given
- Question question = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
-
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
Review review1 = reviewRepository.save(
@@ -65,10 +55,9 @@ class ReviewRepositoryTest {
@Nested
@DisplayName("리뷰 그룹 아이디에 해당하는 리뷰를 생성일 기준 내림차순으로 페이징하여 불러온다")
class FindByReviewGroupIdWithLimit {
-
- private final Question question = questionRepository.save(서술형_필수_질문());
- private final Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- private final Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
private final ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
private final Review review1 = reviewRepository.save(
@@ -160,10 +149,9 @@ class FindByReviewGroupIdWithLimit {
@Nested
@DisplayName("주어진 리뷰보다 오래된 리뷰가 있는지 검사한다")
class ExistsOlderReviewInReviewGroup {
-
- Question question = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
diff --git a/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java
index ab296d796..a3bafe23a 100644
--- a/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java
+++ b/backend/src/test/java/reviewme/review/service/ReviewDetailLookupServiceTest.java
@@ -7,22 +7,14 @@
import static reviewme.fixture.OptionItemFixture.선택지;
import static reviewme.fixture.QuestionFixture.서술형_옵션_질문;
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Answer;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.Review;
@@ -35,9 +27,12 @@
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
import reviewme.template.domain.Section;
import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -52,30 +47,14 @@ class ReviewDetailLookupServiceTest {
@Autowired
private ReviewRepository reviewRepository;
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
@Autowired
private TemplateRepository templateRepository;
@Test
void 리뷰_그룹에_해당하지_않는_리뷰를_조회할_경우_예외가_발생한다() {
// given
- String reviewRequestCode1 = "sancho";
- String groupAccessCode1 = "kirby";
- String reviewRequestCode2 = "aruru";
- String groupAccessCode2 = "tedChang";
- ReviewGroup reviewGroup1 = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode1, groupAccessCode1));
- ReviewGroup reviewGroup2 = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode2, groupAccessCode2));
+ ReviewGroup reviewGroup1 = reviewGroupRepository.save(리뷰_그룹());
+ ReviewGroup reviewGroup2 = reviewGroupRepository.save(리뷰_그룹());
Review review1 = reviewRepository.save(new Review(0, reviewGroup1.getId(), List.of()));
Review review2 = reviewRepository.save(new Review(0, reviewGroup2.getId(), List.of()));
@@ -92,21 +71,19 @@ class ReviewDetailLookupServiceTest {
@Test
void 사용자가_작성한_리뷰를_확인한다() {
// given - 리뷰 그룹 저장
- String reviewRequestCode = "1111";
- String groupAccessCode = "2222";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, groupAccessCode));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
// given - 질문 저장
- Question question1 = questionRepository.save(선택형_필수_질문());
- Question question2 = questionRepository.save(서술형_필수_질문());
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup.getId(), 1));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup.getId(), 2));
+ OptionItem optionItem1 = 선택지();
+ OptionItem optionItem2 = 선택지();
+ OptionGroup optionGroup = 선택지_그룹(List.of(optionItem1, optionItem2));
+ Question question1 = new Question(true, QuestionType.CHECKBOX, optionGroup, "질문1", "설명1", 1);
+ Question question2 = 서술형_필수_질문();
// given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Section section2 = sectionRepository.save(항상_보이는_섹션(List.of(question2.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId(), section2.getId())));
+ Section section1 = 항상_보이는_섹션(List.of(question1));
+ Section section2 = 항상_보이는_섹션(List.of(question2));
+ Template template = templateRepository.save(new Template(List.of(section1, section2)));
// given - 리뷰 답변 저장
List answers = List.of(
@@ -131,14 +108,12 @@ class NotAnsweredOptionalQuestion {
@Test
void 섹션에_필수가_아닌_질문만_있다면_섹션_자체를_반환하지_않는다() {
// given - 리뷰 그룹 저장
- String reviewRequestCode = "sancho";
- String groupAccessCode = "kirby";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, groupAccessCode));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
// given - 질문, 세션, 템플릿 저장
- Question question = questionRepository.save(서술형_옵션_질문(1));
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ Question question = 서술형_옵션_질문(1);
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
// given - 아무것도 응답하지 않은 리뷰 답변 저장
Review review = reviewRepository.save(
@@ -157,15 +132,13 @@ class NotAnsweredOptionalQuestion {
@Test
void 섹션의_다른_질문에_응답했다면_답하지_않은_질문만_반환하지_않는다() {
// given - 리뷰 그룹 저장
- String reviewRequestCode = "aruru";
- String groupAccessCode = "tedChang";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, groupAccessCode));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
// given - 질문, 세션, 템플릿 저장
- Question question1 = questionRepository.save(서술형_옵션_질문(1));
- Question question2 = questionRepository.save(서술형_옵션_질문(2));
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId(), question2.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ Question question1 = 서술형_옵션_질문(1);
+ Question question2 = 서술형_옵션_질문(2);
+ Section section = 항상_보이는_섹션(List.of(question1, question2));
+ Template template = templateRepository.save(new Template(List.of(section)));
// given - 질문 하나에만 응답한 리뷰 답변 저장
TextAnswer textAnswer = new TextAnswer(question1.getId(), "답변".repeat(20));
@@ -182,7 +155,7 @@ class NotAnsweredOptionalQuestion {
.extracting(SectionAnswerResponse::sectionId)
.containsExactly(section.getId()),
() -> assertThat(reviewDetail.sections())
- .flatExtracting(SectionAnswerResponse::questions)
+ .flatExtracting(SectionAnswerResponse::reviews)
.extracting(QuestionAnswerResponse::questionId)
.containsExactly(question1.getId())
);
diff --git a/backend/src/test/java/reviewme/review/service/ReviewGatheredLookupServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewGatheredLookupServiceTest.java
index 141992950..ae89fc67c 100644
--- a/backend/src/test/java/reviewme/review/service/ReviewGatheredLookupServiceTest.java
+++ b/backend/src/test/java/reviewme/review/service/ReviewGatheredLookupServiceTest.java
@@ -3,14 +3,11 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.OptionItemFixture.선택지;
-import static reviewme.fixture.QuestionFixture.서술형_옵션_질문;
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.QuestionFixture.선택형_옵션_질문;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
+import static reviewme.fixture.QuestionFixture.선택형_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
+import static reviewme.fixture.ReviewGroupFixture.템플릿_지정_리뷰_그룹;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
@@ -18,14 +15,6 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.OptionType;
-import reviewme.question.domain.Question;
-import reviewme.question.domain.QuestionType;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
@@ -38,9 +27,13 @@
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.OptionType;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
import reviewme.template.domain.Section;
import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -52,18 +45,6 @@ class ReviewGatheredLookupServiceTest {
@Autowired
private ReviewRepository reviewRepository;
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
@Autowired
private TemplateRepository templateRepository;
@@ -74,47 +55,20 @@ class ReviewGatheredLookupServiceTest {
@BeforeEach
void saveReviewGroup() {
- reviewGroup = reviewGroupRepository.save(리뷰_그룹("1111", "2222"));
+ reviewGroup = reviewGroupRepository.save(리뷰_그룹());
}
@Nested
@DisplayName("섹션에 해당하는 서술형 응답을 질문별로 묶어 반환한다")
class GatherAnswerByQuestionTest {
- @Test
- void 섹션_하위_질문이_하나인_경우() {
- // given - 질문 저장
- Question question1 = questionRepository.save(서술형_필수_질문());
-
- // given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
-
- // given - 리뷰 답변 저장
- TextAnswer answerKB = new TextAnswer(question1.getId(), "커비가 작성한 서술형 답변1");
- TextAnswer answerSC = new TextAnswer(question1.getId(), "산초가 작성한 서술형 답변1");
- reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answerKB)));
- reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answerSC)));
-
- // when
- ReviewsGatheredBySectionResponse actual = reviewLookupService.getReceivedReviewsBySectionId(
- reviewGroup, section1.getId()
- );
-
- // then
- assertThat(actual.reviews().get(0).answers()).extracting(TextResponse::content)
- .containsOnly("커비가 작성한 서술형 답변1", "산초가 작성한 서술형 답변1");
- }
-
@Test
void 섹션_하위_질문이_여러개인_경우() {
- // given - 질문 저장
- Question question1 = questionRepository.save(서술형_필수_질문());
- Question question2 = questionRepository.save(서술형_필수_질문());
-
- // given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId(), question2.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
+ // given - 템플릿 저장
+ Question question1 = 서술형_필수_질문();
+ Question question2 = 서술형_필수_질문();
+ Section section1 = 항상_보이는_섹션(List.of(question1, question2));
+ Template template = templateRepository.save(new Template(List.of(section1)));
// given - 리뷰 답변 저장
TextAnswer answerAR1 = new TextAnswer(question1.getId(), "아루가 작성한 서술형 답변1");
@@ -140,14 +94,12 @@ class GatherAnswerByQuestionTest {
@Test
void 여러개의_섹션이_있는_경우_주어진_섹션ID에_해당하는_것만_반환한다() {
- // given - 질문 저장
- Question question1 = questionRepository.save(서술형_필수_질문());
- Question question2 = questionRepository.save(서술형_필수_질문());
-
- // given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Section section2 = sectionRepository.save(항상_보이는_섹션(List.of(question2.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId(), section2.getId())));
+ // given - 템플릿 저장
+ Question question1 = 서술형_필수_질문();
+ Question question2 = 서술형_필수_질문();
+ Section section1 = 항상_보이는_섹션(List.of(question1));
+ Section section2 = 항상_보이는_섹션(List.of(question2));
+ Template template = templateRepository.save(new Template(List.of(section1, section2)));
// given - 리뷰 답변 저장
TextAnswer answerAR1 = new TextAnswer(question1.getId(), "아루가 작성한 서술형 답변1");
@@ -170,13 +122,11 @@ class GatherAnswerByQuestionTest {
@Test
void 섹션에_필수가_아닌_질문이_있는_경우_답변된_내용만_반환한다() {
- // given - 질문 저장
- Question question1 = questionRepository.save(서술형_옵션_질문());
- Question question2 = questionRepository.save(서술형_옵션_질문());
-
- // given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId(), question2.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
+ // given - 템플릿 저장
+ Question question1 = 서술형_필수_질문();
+ Question question2 = 서술형_필수_질문();
+ Section section1 = 항상_보이는_섹션(List.of(question1, question2));
+ Template template = templateRepository.save(new Template(List.of(section1)));
// given - 리뷰 답변 저장
TextAnswer answerSC1 = new TextAnswer(question1.getId(), "산초가 작성한 서술형 답변1");
@@ -201,12 +151,10 @@ class GatherAnswerByQuestionTest {
@Test
void 질문에_응답이_없는_경우_질문_내용은_반환하되_응답은_빈_배열로_반환한다() {
- // given - 질문 저장
- Question question1 = questionRepository.save(서술형_필수_질문());
-
- // given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
+ // given - 템플릿 저장
+ Question question1 = 서술형_필수_질문();
+ Section section1 = 항상_보이는_섹션(List.of(question1));
+ templateRepository.save(new Template(List.of(section1)));
// when
ReviewsGatheredBySectionResponse actual = reviewLookupService.getReceivedReviewsBySectionId(
@@ -227,22 +175,19 @@ class GatherOptionAnswerByQuestionTest {
@Test
void 섹션_하위_질문이_하나인_경우() {
- // given - 질문 저장
- Question question1 = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionItem optionItem1 = optionItemRepository.save(
- new OptionItem("짜장", optionGroup.getId(), 1, OptionType.CATEGORY));
- OptionItem optionItem2 = optionItemRepository.save(
- new OptionItem("짬뽕", optionGroup.getId(), 2, OptionType.CATEGORY));
-
- // given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
+ // given - 템플릿 저장
+ OptionItem optionItem1 = new OptionItem("짜장", 1, OptionType.CATEGORY);
+ OptionItem optionItem2 = new OptionItem("짬뽕", 2, OptionType.CATEGORY);
+ OptionGroup optionGroup = 선택지_그룹(List.of(optionItem1, optionItem2));
+ Question question1 = new Question(true, QuestionType.CHECKBOX, optionGroup, "선택형 질문", null, 1);
+ Section section1 = 항상_보이는_섹션(List.of(question1));
+ Template template = templateRepository.save(new Template(List.of(section1)));
// given - 리뷰 답변 저장
CheckboxAnswer answer1 = new CheckboxAnswer(question1.getId(), List.of(optionItem1.getId()));
- CheckboxAnswer answer2 = new CheckboxAnswer(question1.getId(),
- List.of(optionItem1.getId(), optionItem2.getId()));
+ CheckboxAnswer answer2 = new CheckboxAnswer(
+ question1.getId(), List.of(optionItem1.getId(), optionItem2.getId())
+ );
reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answer1)));
reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answer2)));
@@ -254,60 +199,20 @@ class GatherOptionAnswerByQuestionTest {
// then
assertThat(actual.reviews().get(0).votes())
.extracting(VoteResponse::content, VoteResponse::count)
- .containsExactlyInAnyOrder(
- tuple("짜장", 2L),
- tuple("짬뽕", 1L)
- );
- }
-
- @Test
- void 섹션_하위_질문이_여러개인_경우() {
- // given - 질문 저장
- Question question1 = questionRepository.save(선택형_옵션_질문());
- Question question2 = questionRepository.save(선택형_옵션_질문());
- OptionGroup optionGroup1 = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionGroup optionGroup2 = optionGroupRepository.save(선택지_그룹(question2.getId()));
- OptionItem optionItem1 = optionItemRepository.save(
- new OptionItem("중식", optionGroup1.getId(), 1, OptionType.CATEGORY));
- OptionItem optionItem2 = optionItemRepository.save(
- new OptionItem("분식", optionGroup2.getId(), 2, OptionType.CATEGORY));
-
- // given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId(), question2.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
-
- // given - 리뷰 답변 저장
- CheckboxAnswer answer1 = new CheckboxAnswer(question1.getId(), List.of(optionItem1.getId()));
- CheckboxAnswer answer2 = new CheckboxAnswer(question2.getId(), List.of(optionItem2.getId()));
- reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answer1, answer2)));
-
- // when
- ReviewsGatheredBySectionResponse actual = reviewLookupService.getReceivedReviewsBySectionId(
- reviewGroup, section1.getId()
- );
-
- // then
- assertThat(actual.reviews().get(0).votes())
- .extracting(VoteResponse::content, VoteResponse::count)
- .containsOnly(tuple("중식", 1L));
- assertThat(actual.reviews().get(1).votes())
- .extracting(VoteResponse::content, VoteResponse::count)
- .containsOnly(tuple("분식", 1L));
+ .containsExactlyInAnyOrder(tuple("짜장", 2L), tuple("짬뽕", 1L));
}
@Test
void 아무도_고르지_않은_선택지는_0개로_계산하여_반환한다() {
// given - 질문 저장
- Question question1 = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question1.getId()));
- OptionItem optionItem1 = optionItemRepository.save(
- new OptionItem("우테코 산초", optionGroup.getId(), 1, OptionType.CATEGORY));
- OptionItem optionItem2 = optionItemRepository.save(
- new OptionItem("제이든 산초", optionGroup.getId(), 2, OptionType.CATEGORY));
+ OptionItem optionItem1 = new OptionItem("우테코 산초", 1, OptionType.CATEGORY);
+ OptionItem optionItem2 = new OptionItem("제이든 산초", 2, OptionType.CATEGORY);
+ OptionGroup optionGroup = 선택지_그룹(List.of(optionItem1, optionItem2));
+ Question question1 = new Question(false, QuestionType.CHECKBOX, optionGroup, "선택형 질문", null, 1);
// given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
+ Section section1 = 항상_보이는_섹션(List.of(question1));
+ Template template = templateRepository.save(new Template(List.of(section1)));
// given - 리뷰 답변 저장
CheckboxAnswer answer1 = new CheckboxAnswer(question1.getId(), List.of(optionItem1.getId()));
@@ -323,30 +228,27 @@ class GatherOptionAnswerByQuestionTest {
// then
assertThat(actual.reviews().get(0).votes())
.extracting(VoteResponse::content, VoteResponse::count)
- .containsExactlyInAnyOrder(
- tuple("우테코 산초", 2L),
- tuple("제이든 산초", 0L)
- );
+ .containsExactlyInAnyOrder(tuple("우테코 산초", 2L), tuple("제이든 산초", 0L));
}
}
@Test
void 서술형_질문에_대한_응답과_선택형_질문에_대한_응답을_함께_반환한다() {
// given - 질문 저장
- Question question1 = questionRepository.save(서술형_필수_질문());
- Question question2 = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question2.getId()));
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup.getId()));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup.getId()));
+ Question question1 = 서술형_필수_질문();
+ Question question2 = 선택형_질문(true, 2, 2);
+ List optionItems = question2.getOptionGroup().getOptionItems();
// given - 섹션, 템플릿 저장
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId(), question2.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
+ Section section1 = 항상_보이는_섹션(List.of(question1, question2));
+ Template template = templateRepository.save(new Template(List.of(section1)));
// given - 리뷰 답변 저장
TextAnswer answer1 = new TextAnswer(question1.getId(), "아루가 작성한 서술형 답변");
- CheckboxAnswer answer2 = new CheckboxAnswer(question2.getId(),
- List.of(optionItem1.getId(), optionItem2.getId()));
+ CheckboxAnswer answer2 = new CheckboxAnswer(
+ question2.getId(),
+ optionItems.stream().map(OptionItem::getId).toList() // check all options
+ );
reviewRepository.save(new Review(template.getId(), reviewGroup.getId(), List.of(answer1, answer2)));
// when
@@ -367,8 +269,8 @@ class GatherOptionAnswerByQuestionTest {
assertThat(actual.reviews().get(1).votes())
.extracting(VoteResponse::content, VoteResponse::count)
.containsExactlyInAnyOrder(
- tuple(optionItem1.getContent(), 1L),
- tuple(optionItem2.getContent(), 1L)
+ tuple(optionItems.get(0).getContent(), 1L),
+ tuple(optionItems.get(1).getContent(), 1L)
);
assertThat(actual.reviews().get(1).answers()).isNull();
}
@@ -376,15 +278,12 @@ class GatherOptionAnswerByQuestionTest {
@Test
void 다른_사람이_받은_리뷰는_포함하지_않는다() {
// given - 질문 저장
- Question question1 = questionRepository.save(서술형_필수_질문());
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
-
- String reviewRequestCodeBE = "review_me_be";
- ReviewGroup reviewGroupBE = new ReviewGroup("reviewee", "projectName",
- reviewRequestCodeBE, "groupAccessCode", template.getId());
- ReviewGroup reviewGroupFE = new ReviewGroup("reviewee", "projectName",
- "reviewRequestCode", "groupAccessCode", template.getId());
+ Question question1 = 서술형_필수_질문();
+ Section section1 = 항상_보이는_섹션(List.of(question1));
+ Template template = templateRepository.save(new Template(List.of(section1)));
+
+ ReviewGroup reviewGroupBE = 템플릿_지정_리뷰_그룹(template.getId());
+ ReviewGroup reviewGroupFE = 템플릿_지정_리뷰_그룹(template.getId());
reviewGroupRepository.saveAll(List.of(reviewGroupFE, reviewGroupBE));
// given - 리뷰 답변 저장
@@ -404,24 +303,24 @@ class GatherOptionAnswerByQuestionTest {
@Test
void 질문을_position순서대로_반환한다() {
// given
- Question question1 = questionRepository.save(new Question(false, QuestionType.TEXT, "질문1", null, 3));
- Question question2 = questionRepository.save(new Question(false, QuestionType.TEXT, "질문2", null, 4));
- Question question3 = questionRepository.save(new Question(false, QuestionType.TEXT, "질문3", null, 1));
- Question question4 = questionRepository.save(new Question(false, QuestionType.TEXT, "질문4", null, 2));
-
- Section section1 = sectionRepository.save(항상_보이는_섹션(
- List.of(question1.getId(), question2.getId(), question3.getId(), question4.getId())));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
+ Question question1 = new Question(false, QuestionType.TEXT, "질문1", null, 3);
+ Question question2 = new Question(false, QuestionType.TEXT, "질문2", null, 4);
+ Question question3 = new Question(false, QuestionType.TEXT, "질문3", null, 1);
+ Question question4 = new Question(false, QuestionType.TEXT, "질문4", null, 2);
+ Section section1 = 항상_보이는_섹션(List.of(question1, question2, question3, question4));
+ templateRepository.save(new Template(List.of(section1)));
// when
ReviewsGatheredBySectionResponse actual = reviewLookupService.getReceivedReviewsBySectionId(
- reviewGroup, section1.getId());
+ reviewGroup, section1.getId()
+ );
// then
assertThat(actual.reviews())
.extracting(ReviewsGatheredByQuestionResponse::question)
.extracting(SimpleQuestionResponse::name)
- .containsExactly(question3.getContent(), question4.getContent(),
- question1.getContent(), question2.getContent());
+ .containsExactly(
+ question3.getContent(), question4.getContent(), question1.getContent(), question2.getContent()
+ );
}
}
diff --git a/backend/src/test/java/reviewme/review/service/ReviewListLookupServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewListLookupServiceTest.java
index d8384afe5..0d40839be 100644
--- a/backend/src/test/java/reviewme/review/service/ReviewListLookupServiceTest.java
+++ b/backend/src/test/java/reviewme/review/service/ReviewListLookupServiceTest.java
@@ -2,34 +2,19 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
-import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.OptionItemFixture.선택지;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
-import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
import reviewme.review.repository.ReviewRepository;
-import reviewme.review.service.dto.response.list.ReceivedReviewsResponse;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageResponse;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
@ServiceTest
class ReviewListLookupServiceTest {
@@ -37,52 +22,26 @@ class ReviewListLookupServiceTest {
@Autowired
private ReviewListLookupService reviewListLookupService;
- @Autowired
- private QuestionRepository questionRepository;
-
@Autowired
private ReviewGroupRepository reviewGroupRepository;
- @Autowired
- private OptionItemRepository optionItemRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private TemplateRepository templateRepository;
-
@Autowired
private ReviewRepository reviewRepository;
@Test
void 확인_코드에_해당하는_그룹이_존재하면_내가_받은_리뷰_목록을_반환한다() {
// given - 리뷰 그룹 저장
- String reviewRequestCode = "reviewRequestCode";
- String groupAccessCode = "groupAccessCode";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, groupAccessCode));
-
- // given - 질문 저장
- Question question = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question.getId()));
- OptionItem categoryOption = optionItemRepository.save(선택지(optionGroup.getId(), 1));
-
- // given - 섹션, 템플릿 저장
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
// given - 리뷰 답변 저장
- CheckboxAnswer categoryAnswer = new CheckboxAnswer(question.getId(), List.of(categoryOption.getId()));
- Review review1 = new Review(template.getId(), reviewGroup.getId(), List.of(categoryAnswer));
- TextAnswer textAnswer = new TextAnswer(question.getId(), "텍스트형 응답");
- Review review2 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
+ CheckboxAnswer categoryAnswer = new CheckboxAnswer(1L, List.of(1L));
+ Review review1 = new Review(1L, reviewGroup.getId(), List.of(categoryAnswer));
+ TextAnswer textAnswer = new TextAnswer(1L, "텍스트형 응답");
+ Review review2 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
reviewRepository.saveAll(List.of(review1, review2));
// when
- ReceivedReviewsResponse response = reviewListLookupService.getReceivedReviews(
+ ReceivedReviewPageResponse response = reviewListLookupService.getReceivedReviews(
Long.MAX_VALUE, 5, reviewGroup
);
@@ -97,26 +56,17 @@ class ReviewListLookupServiceTest {
@Test
void 내가_받은_리뷰_목록을_페이지네이션을_적용하여_반환한다() {
// given - 리뷰 그룹 저장
- String reviewRequestCode = "reviewRequestCode";
- String groupAccessCode = "groupAccessCode";
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹(reviewRequestCode, groupAccessCode));
-
- // given - 질문 저장
- Question question = questionRepository.save(선택형_필수_질문());
-
- // given - 섹션, 템플릿 저장
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
// given - 리뷰 답변 저장
- TextAnswer textAnswer = new TextAnswer(question.getId(), "텍스트형 응답");
- Review review1 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review2 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review3 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
+ TextAnswer textAnswer = new TextAnswer(1L, "텍스트형 응답");
+ Review review1 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review2 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review3 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
reviewRepository.saveAll(List.of(review1, review2, review3));
// when
- ReceivedReviewsResponse response
+ ReceivedReviewPageResponse response
= reviewListLookupService.getReceivedReviews(Long.MAX_VALUE, 2, reviewGroup);
// then
diff --git a/backend/src/test/java/reviewme/review/service/ReviewRegisterServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewRegisterServiceTest.java
index 73ab64897..7d65c2a17 100644
--- a/backend/src/test/java/reviewme/review/service/ReviewRegisterServiceTest.java
+++ b/backend/src/test/java/reviewme/review/service/ReviewRegisterServiceTest.java
@@ -6,21 +6,13 @@
import static reviewme.fixture.OptionItemFixture.선택지;
import static reviewme.fixture.QuestionFixture.서술형_옵션_질문;
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.조건부로_보이는_섹션;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
@@ -30,9 +22,12 @@
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
import reviewme.template.domain.Section;
import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -41,15 +36,6 @@ class ReviewRegisterServiceTest {
@Autowired
private ReviewRegisterService reviewRegisterService;
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
@Autowired
private ReviewGroupRepository reviewGroupRepository;
@@ -59,38 +45,33 @@ class ReviewRegisterServiceTest {
@Autowired
private ReviewRepository reviewRepository;
- @Autowired
- private SectionRepository sectionRepository;
-
@Test
void 요청한_내용으로_리뷰를_등록한다() {
// given
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- Question requiredCheckQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup requiredOptionGroup = optionGroupRepository.save(선택지_그룹(requiredCheckQuestion.getId()));
- OptionItem requiredOptionItem1 = optionItemRepository.save(선택지(requiredOptionGroup.getId()));
- OptionItem requiredOptionItem2 = optionItemRepository.save(선택지(requiredOptionGroup.getId()));
- Section visibleSection = sectionRepository.save(항상_보이는_섹션(List.of(requiredCheckQuestion.getId()), 1));
-
- Question requiredTextQuestion = questionRepository.save(서술형_필수_질문());
- Question conditionalCheckQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup conditionalOptionGroup = optionGroupRepository.save(선택지_그룹(conditionalCheckQuestion.getId()));
- OptionItem conditionalOptionItem1 = optionItemRepository.save(선택지(conditionalOptionGroup.getId()));
- OptionItem conditionalOptionItem2 = optionItemRepository.save(선택지(conditionalOptionGroup.getId()));
- Section conditionalSection = sectionRepository.save(조건부로_보이는_섹션(
- List.of(requiredCheckQuestion.getId(), requiredTextQuestion.getId(), conditionalCheckQuestion.getId()),
- requiredOptionItem1.getId(), 2)
+ OptionItem requiredOptionItem1 = 선택지();
+ OptionItem requiredOptionItem2 = 선택지();
+ OptionGroup requiredOptionGroup = 선택지_그룹(List.of(requiredOptionItem1, requiredOptionItem2));
+ Question requiredCheckQuestion = new Question(true, QuestionType.CHECKBOX, requiredOptionGroup, "질문", "설명", 1);
+ Section visibleSection = 항상_보이는_섹션(List.of(requiredCheckQuestion), 1);
+
+ OptionItem conditionalOptionItem1 = 선택지();
+ OptionItem conditionalOptionItem2 = 선택지();
+ OptionGroup conditionalOptionGroup = 선택지_그룹(List.of(conditionalOptionItem1, conditionalOptionItem2));
+ Question requiredTextQuestion = 서술형_필수_질문();
+ Question conditionalCheckQuestion = new Question(false, QuestionType.CHECKBOX, conditionalOptionGroup, "질문",
+ "설명", 1);
+ Section conditionalSection = 조건부로_보이는_섹션(
+ List.of(requiredCheckQuestion, requiredTextQuestion, conditionalCheckQuestion), requiredOptionItem1, 2
);
- Question optionalTextQuestion = questionRepository.save(서술형_옵션_질문());
- Section visibleOptionalSection = sectionRepository.save(항상_보이는_섹션(
- List.of(optionalTextQuestion.getId()), 3)
+ Question optionalTextQuestion = 서술형_옵션_질문();
+ Section visibleOptionalSection = 항상_보이는_섹션(List.of(optionalTextQuestion), 3);
+ templateRepository.save(
+ new Template(List.of(visibleSection, conditionalSection, visibleOptionalSection))
);
- Template template = templateRepository.save(템플릿(
- List.of(visibleSection.getId(), conditionalSection.getId(), visibleOptionalSection.getId())));
-
ReviewAnswerRequest requiredCheckQuestionAnswer = new ReviewAnswerRequest(
requiredCheckQuestion.getId(), List.of(requiredOptionItem1.getId()), null);
ReviewAnswerRequest requiredTextQuestionAnswer = new ReviewAnswerRequest(
diff --git a/backend/src/test/java/reviewme/review/service/ReviewSummaryServiceTest.java b/backend/src/test/java/reviewme/review/service/ReviewSummaryServiceTest.java
index 2a2ffa7a5..c7bbd1660 100644
--- a/backend/src/test/java/reviewme/review/service/ReviewSummaryServiceTest.java
+++ b/backend/src/test/java/reviewme/review/service/ReviewSummaryServiceTest.java
@@ -5,22 +5,19 @@
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Review;
import reviewme.review.repository.ReviewRepository;
import reviewme.review.service.dto.response.list.ReceivedReviewsSummaryResponse;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.Question;
import reviewme.template.domain.Section;
import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -38,21 +35,15 @@ class ReviewSummaryServiceTest {
@Autowired
private TemplateRepository templateRepository;
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
@Test
void 리뷰_그룹에_등록된_리뷰_요약_정보를_반환한다() {
// given
- Question question = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ Template template = templateRepository.save(new Template(List.of(section)));
ReviewGroup reviewGroup1 = reviewGroupRepository.save(리뷰_그룹());
- ReviewGroup reviewGroup2 = reviewGroupRepository.save(리뷰_그룹("reReCo", "groupCo"));
+ ReviewGroup reviewGroup2 = reviewGroupRepository.save(리뷰_그룹());
List reviews = List.of(
new Review(template.getId(), reviewGroup1.getId(), List.of()),
diff --git a/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperFactoryTest.java b/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperFactoryTest.java
index 25d1b5018..f18dc74f3 100644
--- a/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperFactoryTest.java
+++ b/backend/src/test/java/reviewme/review/service/mapper/AnswerMapperFactoryTest.java
@@ -8,9 +8,9 @@
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
-import reviewme.question.domain.QuestionType;
import reviewme.review.domain.Answer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
+import reviewme.template.domain.QuestionType;
@ExtendWith(OutputCaptureExtension.class)
class AnswerMapperFactoryTest {
@@ -18,13 +18,13 @@ class AnswerMapperFactoryTest {
private final AnswerMapper answerMapper = new AnswerMapper() {
@Override
- public boolean supports(QuestionType questionType) {
- return questionType == QuestionType.CHECKBOX;
+ public Answer mapToAnswer(ReviewAnswerRequest answerRequest) {
+ return null;
}
@Override
- public Answer mapToAnswer(ReviewAnswerRequest answerRequest) {
- return null;
+ public boolean supports(QuestionType questionType) {
+ return questionType == QuestionType.CHECKBOX;
}
};
diff --git a/backend/src/test/java/reviewme/review/service/mapper/CheckboxAnswerMapperTest.java b/backend/src/test/java/reviewme/review/service/mapper/CheckboxAnswerMapperTest.java
index eb2d96f98..c05b4553f 100644
--- a/backend/src/test/java/reviewme/review/service/mapper/CheckboxAnswerMapperTest.java
+++ b/backend/src/test/java/reviewme/review/service/mapper/CheckboxAnswerMapperTest.java
@@ -1,14 +1,14 @@
package reviewme.review.service.mapper;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.List;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.CheckboxAnswerSelectedOption;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
-import reviewme.review.service.exception.CheckBoxAnswerIncludedTextException;
class CheckboxAnswerMapperTest {
@@ -28,16 +28,17 @@ class CheckboxAnswerMapperTest {
.containsExactly(1L, 2L, 3L);
}
- @Test
- void 체크박스_답변_요청에_텍스트가_포함되어_있으면_예외를_발생시킨다() {
+ @ParameterizedTest
+ @NullAndEmptySource
+ void 체크박스_답변이_비어있는_경우_null로_매핑한다(List selectedOptionIds) {
// given
- ReviewAnswerRequest request = new ReviewAnswerRequest(1L, List.of(1L, 2L, 3L), "text");
+ ReviewAnswerRequest request = new ReviewAnswerRequest(1L, selectedOptionIds, null);
+ CheckboxAnswerMapper mapper = new CheckboxAnswerMapper();
// when
- CheckboxAnswerMapper mapper = new CheckboxAnswerMapper();
+ CheckboxAnswer actual = mapper.mapToAnswer(request);
// then
- assertThatThrownBy(() -> mapper.mapToAnswer(request))
- .isInstanceOf(CheckBoxAnswerIncludedTextException.class);
+ assertThat(actual).isNull();
}
}
diff --git a/backend/src/test/java/reviewme/review/service/mapper/ReviewGatherMapperTest.java b/backend/src/test/java/reviewme/review/service/mapper/ReviewGatherMapperTest.java
index 1e411f13e..1ce27dc25 100644
--- a/backend/src/test/java/reviewme/review/service/mapper/ReviewGatherMapperTest.java
+++ b/backend/src/test/java/reviewme/review/service/mapper/ReviewGatherMapperTest.java
@@ -2,22 +2,15 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
-import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.OptionItemFixture.선택지;
import static reviewme.fixture.QuestionFixture.서술형_옵션_질문;
-import static reviewme.fixture.QuestionFixture.선택형_옵션_질문;
+import static reviewme.fixture.QuestionFixture.선택형_질문;
+import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
import java.util.List;
import java.util.Map;
import org.assertj.core.groups.Tuple;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
@@ -28,7 +21,11 @@
import reviewme.review.service.dto.response.gathered.TextResponse;
import reviewme.review.service.dto.response.gathered.VoteResponse;
import reviewme.support.ServiceTest;
-import reviewme.template.repository.SectionRepository;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
+import reviewme.template.repository.TemplateRepository;
@ServiceTest
class ReviewGatherMapperTest {
@@ -37,16 +34,7 @@ class ReviewGatherMapperTest {
private ReviewGatherMapper reviewGatherMapper;
@Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
+ private TemplateRepository templateRepository;
@Autowired
private ReviewRepository reviewRepository;
@@ -54,23 +42,23 @@ class ReviewGatherMapperTest {
@Test
void 질문과_하위_답변을_규칙에_맞게_반환한다() {
// given
- Question question1 = questionRepository.save(서술형_옵션_질문(1));
- Question question2 = questionRepository.save(선택형_옵션_질문(2));
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question2.getId()));
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup.getId()));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup.getId()));
- optionItemRepository.saveAll(List.of(optionItem1, optionItem2));
+ Question question1 = 서술형_옵션_질문(1);
+ Question question2 = 선택형_질문(false, 2, 2);
+ Section section = 항상_보이는_섹션(List.of(question1, question2));
+ Template template = templateRepository.save(new Template(List.of(section)));
+ List optionItems = question2.getOptionGroup().getOptionItems();
TextAnswer textAnswer1 = new TextAnswer(question1.getId(), "프엔 서술형 답변");
- TextAnswer textAnswer2 = new TextAnswer(question1.getId(), "백엔드 서술형 답변");
- CheckboxAnswer checkboxAnswer = new CheckboxAnswer(
- question2.getId(), List.of(optionItem1.getId(), optionItem2.getId()));
- reviewRepository.save(new Review(1L, 1L, List.of(textAnswer1, textAnswer2, checkboxAnswer)));
+ CheckboxAnswer checkboxAnswer1 = new CheckboxAnswer(
+ question2.getId(),
+ optionItems.stream().map(OptionItem::getId).toList() // check all options
+ );
+ reviewRepository.save(new Review(template.getId(), 1L, List.of(textAnswer1, checkboxAnswer1)));
// when
ReviewsGatheredBySectionResponse actual = reviewGatherMapper.mapToReviewsGatheredBySection(Map.of(
- question1, List.of(textAnswer1, textAnswer2),
- question2, List.of(checkboxAnswer)),
+ question1, List.of(textAnswer1),
+ question2, List.of(checkboxAnswer1)),
List.of()
);
@@ -78,32 +66,30 @@ class ReviewGatherMapperTest {
assertAll(
() -> 질문의_수만큼_반환한다(actual, 2),
() -> 질문의_내용을_반환한다(actual, question1.getContent(), question2.getContent()),
- () -> 서술형_답변을_반환한다(actual, "프엔 서술형 답변", "백엔드 서술형 답변"),
+ () -> 서술형_답변을_반환한다(actual, "프엔 서술형 답변"),
() -> 선택형_답변을_반환한다(actual,
- Tuple.tuple(optionItem1.getContent(), 1L),
- Tuple.tuple(optionItem2.getContent(), 1L))
+ Tuple.tuple(optionItems.get(0).getContent(), 1L),
+ Tuple.tuple(optionItems.get(1).getContent(), 1L))
);
}
@Test
void 서술형_질문에_답변이_없으면_질문_정보는_반환하되_답변은_빈_배열로_반환한다() {
// given
- Question question1 = questionRepository.save(서술형_옵션_질문(1));
- Question question2 = questionRepository.save(서술형_옵션_질문(2));
+ Question question = 서술형_옵션_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ templateRepository.save(new Template(List.of(section)));
// when
ReviewsGatheredBySectionResponse actual = reviewGatherMapper.mapToReviewsGatheredBySection(
- Map.of(
- question1, List.of(),
- question2, List.of()
- ),
+ Map.of(question, List.of()),
List.of()
);
// then
assertAll(
- () -> 질문의_수만큼_반환한다(actual, 2),
- () -> 질문의_내용을_반환한다(actual, question1.getContent(), question2.getContent()),
+ () -> 질문의_수만큼_반환한다(actual, 1),
+ () -> 질문의_내용을_반환한다(actual, question.getContent()),
() -> assertThat(actual.reviews())
.flatExtracting(ReviewsGatheredByQuestionResponse::answers)
.isEmpty()
diff --git a/backend/src/test/java/reviewme/review/service/mapper/ReviewListMapperTest.java b/backend/src/test/java/reviewme/review/service/mapper/ReviewListMapperTest.java
index 0cdfe0a32..88820f3f2 100644
--- a/backend/src/test/java/reviewme/review/service/mapper/ReviewListMapperTest.java
+++ b/backend/src/test/java/reviewme/review/service/mapper/ReviewListMapperTest.java
@@ -2,27 +2,18 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
-import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
import reviewme.review.repository.ReviewRepository;
-import reviewme.review.service.dto.response.list.ReviewListElementResponse;
+import reviewme.review.service.dto.response.list.ReceivedReviewPageElementResponse;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
@ServiceTest
class ReviewListMapperTest {
@@ -30,18 +21,9 @@ class ReviewListMapperTest {
@Autowired
private ReviewListMapper reviewListMapper;
- @Autowired
- private QuestionRepository questionRepository;
-
@Autowired
private ReviewGroupRepository reviewGroupRepository;
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private TemplateRepository templateRepository;
-
@Autowired
private ReviewRepository reviewRepository;
@@ -50,25 +32,18 @@ class ReviewListMapperTest {
// given - 리뷰 그룹
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- // given - 질문 저장
- Question question = questionRepository.save(선택형_필수_질문());
-
- // given - 섹션, 템플릿 저장
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
-
// given - 리뷰 답변 저장
- TextAnswer textAnswer = new TextAnswer(question.getId(), "텍스트형 응답");
- Review review1 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review2 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review3 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review4 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review5 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review6 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review7 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review8 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review9 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
- Review review10 = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
+ TextAnswer textAnswer = new TextAnswer(1L, "텍스트형 응답");
+ Review review1 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review2 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review3 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review4 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review5 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review6 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review7 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review8 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review9 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
+ Review review10 = new Review(1L, reviewGroup.getId(), List.of(textAnswer));
reviewRepository.saveAll(
List.of(review1, review2, review3, review4, review5, review6, review7, review8, review9, review10));
@@ -76,13 +51,13 @@ class ReviewListMapperTest {
int size = 5;
// when
- List responses = reviewListMapper.mapToReviewList(
+ List responses = reviewListMapper.mapToReviewList(
reviewGroup, lastReviewId, size);
// then
assertAll(
() -> assertThat(responses).hasSize(size),
- () -> assertThat(responses).extracting(ReviewListElementResponse::reviewId)
+ () -> assertThat(responses).extracting(ReceivedReviewPageElementResponse::reviewId)
.containsExactly(
review7.getId(), review6.getId(), review5.getId(), review4.getId(), review3.getId())
);
diff --git a/backend/src/test/java/reviewme/review/service/mapper/ReviewMapperTest.java b/backend/src/test/java/reviewme/review/service/mapper/ReviewMapperTest.java
index 8ca15f312..4c28eafb3 100644
--- a/backend/src/test/java/reviewme/review/service/mapper/ReviewMapperTest.java
+++ b/backend/src/test/java/reviewme/review/service/mapper/ReviewMapperTest.java
@@ -2,37 +2,28 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.OptionItemFixture.선택지;
import static reviewme.fixture.QuestionFixture.서술형_옵션_질문;
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.QuestionFixture.선택형_옵션_질문;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
+import static reviewme.fixture.QuestionFixture.선택형_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
import reviewme.review.service.dto.request.ReviewRegisterRequest;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
import reviewme.template.domain.Section;
-import reviewme.template.repository.SectionRepository;
+import reviewme.template.domain.Template;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -44,34 +35,26 @@ class ReviewMapperTest {
@Autowired
private ReviewGroupRepository reviewGroupRepository;
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
@Autowired
private TemplateRepository templateRepository;
@Test
- void 텍스트가_포함된_리뷰를_생성한다() {
+ void 서술형_답변을_매핑한다() {
// given
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- Question question = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- templateRepository.save(템플릿(List.of(section.getId())));
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ templateRepository.save(new Template(List.of(section)));
String expectedTextAnswer = "답".repeat(20);
- ReviewAnswerRequest reviewAnswerRequest = new ReviewAnswerRequest(question.getId(), null, expectedTextAnswer);
- ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(reviewGroup.getReviewRequestCode(),
- List.of(reviewAnswerRequest));
+ ReviewAnswerRequest reviewAnswerRequest = new ReviewAnswerRequest(
+ question.getId(), null, expectedTextAnswer
+ );
+ ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(
+ reviewGroup.getReviewRequestCode(),
+ List.of(reviewAnswerRequest)
+ );
// when
Review review = reviewMapper.mapToReview(reviewRegisterRequest);
@@ -81,22 +64,21 @@ class ReviewMapperTest {
}
@Test
- void 체크박스가_포함된_리뷰를_생성한다() {
+ void 선택형_답변을_매핑한다() {
// given
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ Question question = 선택형_질문(true, 2, 1);
+ Section section = 항상_보이는_섹션(List.of(question));
+ templateRepository.save(new Template(List.of(section)));
- Question question = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question.getId()));
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup.getId()));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup.getId()));
-
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewAnswerRequest reviewAnswerRequest = new ReviewAnswerRequest(question.getId(),
- List.of(optionItem1.getId()), null);
- ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(reviewGroup.getReviewRequestCode(),
- List.of(reviewAnswerRequest));
+ OptionItem optionItem = question.getOptionGroup().getOptionItems().get(0);
+ ReviewAnswerRequest reviewAnswerRequest = new ReviewAnswerRequest(
+ question.getId(), List.of(optionItem.getId()), null
+ );
+ ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(
+ reviewGroup.getReviewRequestCode(),
+ List.of(reviewAnswerRequest)
+ );
// when
Review review = reviewMapper.mapToReview(reviewRegisterRequest);
@@ -106,68 +88,60 @@ class ReviewMapperTest {
}
@Test
- void 필수가_아닌_질문에_답변이_없을_경우_답변을_생성하지_않는다() {
+ void 필수가_아닌_서술형_질문에_답변이_없으면_매핑하지_않는다() {
// given
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ Question question = 서술형_옵션_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ templateRepository.save(new Template(List.of(section)));
- Question requiredTextQuestion = questionRepository.save(서술형_필수_질문());
- Question optionalTextQuestion = questionRepository.save(서술형_옵션_질문());
+ ReviewAnswerRequest answerRequest = new ReviewAnswerRequest(question.getId(), null, "");
+ ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(
+ reviewGroup.getReviewRequestCode(), List.of(answerRequest)
+ );
- Question requeiredCheckBoxQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup optionGroup1 = optionGroupRepository.save(선택지_그룹(requeiredCheckBoxQuestion.getId()));
- OptionItem optionItem1 = optionItemRepository.save(선택지(optionGroup1.getId()));
- OptionItem optionItem2 = optionItemRepository.save(선택지(optionGroup1.getId()));
+ // when
+ Review review = reviewMapper.mapToReview(reviewRegisterRequest);
- Question optionalCheckBoxQuestion = questionRepository.save(선택형_옵션_질문());
- OptionGroup optionGroup2 = optionGroupRepository.save(선택지_그룹(optionalCheckBoxQuestion.getId()));
- OptionItem optionItem3 = optionItemRepository.save(선택지(optionGroup2.getId()));
- OptionItem optionItem4 = optionItemRepository.save(선택지(optionGroup2.getId()));
+ // then
+ assertThat(review.getAnswersByType(TextAnswer.class))
+ .extracting(TextAnswer::getQuestionId)
+ .isEmpty();
+ }
- Section section = sectionRepository.save(항상_보이는_섹션(
- List.of(requiredTextQuestion.getId(), optionalTextQuestion.getId(),
- requeiredCheckBoxQuestion.getId(), optionalCheckBoxQuestion.getId())));
- templateRepository.save(템플릿(List.of(section.getId())));
+ @Test
+ void 필수가_아닌_선택형_질문에_답변이_없으면_매핑하지_않는다() {
+ // given
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ Question question = 선택형_질문(false, 2, 1);
+ Section section = 항상_보이는_섹션(List.of(question));
+ templateRepository.save(new Template(List.of(section)));
- String textAnswer = "답".repeat(20);
- ReviewAnswerRequest requiredTextAnswerRequest = new ReviewAnswerRequest(
- requiredTextQuestion.getId(), null, textAnswer
- );
- ReviewAnswerRequest optionalTextAnswerRequest = new ReviewAnswerRequest(
- optionalTextQuestion.getId(), null, ""
- );
- ReviewAnswerRequest requiredCheckBoxAnswerRequest = new ReviewAnswerRequest(
- requeiredCheckBoxQuestion.getId(), List.of(optionItem1.getId()), null
- );
- ReviewAnswerRequest optionalCheckBoxAnswerRequest = new ReviewAnswerRequest(
- optionalCheckBoxQuestion.getId(), List.of(), null
+ ReviewAnswerRequest answerRequest = new ReviewAnswerRequest(question.getId(), List.of(), null);
+ ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(
+ reviewGroup.getReviewRequestCode(), List.of(answerRequest)
);
- ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(reviewGroup.getReviewRequestCode(),
- List.of(requiredTextAnswerRequest, optionalTextAnswerRequest,
- requiredCheckBoxAnswerRequest, optionalCheckBoxAnswerRequest));
// when
Review review = reviewMapper.mapToReview(reviewRegisterRequest);
// then
- assertAll(
- () -> assertThat(review.getAnswersByType(TextAnswer.class))
- .extracting(TextAnswer::getQuestionId)
- .containsExactly(requiredTextQuestion.getId()),
- () -> assertThat(review.getAnswersByType(CheckboxAnswer.class))
- .extracting(CheckboxAnswer::getQuestionId)
- .containsExactly(requeiredCheckBoxQuestion.getId())
- );
+ assertThat(review.getAnswersByType(CheckboxAnswer.class))
+ .extracting(CheckboxAnswer::getQuestionId)
+ .isEmpty();
}
@Test
void 잘못된_리뷰_요청_코드로_리뷰를_생성할_경우_예외가_발생한다() {
// given
String reviewRequestCode = "notExistCode";
- Question savedQuestion = questionRepository.save(서술형_필수_질문());
+ Question savedQuestion = 서술형_필수_질문();
ReviewAnswerRequest emptyTextReviewRequest = new ReviewAnswerRequest(
- savedQuestion.getId(), null, "");
+ savedQuestion.getId(), null, ""
+ );
ReviewRegisterRequest reviewRegisterRequest = new ReviewRegisterRequest(
- reviewRequestCode, List.of(emptyTextReviewRequest));
+ reviewRequestCode, List.of(emptyTextReviewRequest)
+ );
// when, then
assertThatThrownBy(() -> reviewMapper.mapToReview(reviewRegisterRequest))
diff --git a/backend/src/test/java/reviewme/review/service/mapper/TextAnswerMapperTest.java b/backend/src/test/java/reviewme/review/service/mapper/TextAnswerMapperTest.java
index 841e2d5a3..b7fc960cf 100644
--- a/backend/src/test/java/reviewme/review/service/mapper/TextAnswerMapperTest.java
+++ b/backend/src/test/java/reviewme/review/service/mapper/TextAnswerMapperTest.java
@@ -1,23 +1,16 @@
package reviewme.review.service.mapper;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import java.util.List;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
import reviewme.review.domain.TextAnswer;
import reviewme.review.service.dto.request.ReviewAnswerRequest;
-import reviewme.review.service.exception.TextAnswerIncludedOptionItemException;
class TextAnswerMapperTest {
- /*
- TODO: Request를 추상화해야 할까요?
- 떠오르는 방법은 아래와 같습니다.
- 1: static factory method를 사용 -> 걷잡을 수 없어지지 않을까요?
- 2: 다른 방식으로 추상화 ?
- */
-
@Test
void 텍스트_답변을_요청으로부터_매핑한다() {
// given
@@ -31,16 +24,18 @@ class TextAnswerMapperTest {
assertThat(actual.getContent()).isEqualTo("text");
}
- @Test
- void 텍스트_답변_요청에_옵션이_포함되어_있으면_예외를_발생시킨다() {
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(strings = {"", " "})
+ void 텍스트_답변이_비어있는_경우_null로_매핑한다(String text) {
// given
- ReviewAnswerRequest request = new ReviewAnswerRequest(1L, List.of(1L), "text");
+ ReviewAnswerRequest request = new ReviewAnswerRequest(1L, null, text);
// when
TextAnswerMapper mapper = new TextAnswerMapper();
+ TextAnswer actual = mapper.mapToAnswer(request);
// then
- assertThatThrownBy(() -> mapper.mapToAnswer(request))
- .isInstanceOf(TextAnswerIncludedOptionItemException.class);
+ assertThat(actual).isNull();
}
}
diff --git a/backend/src/test/java/reviewme/review/service/validator/AnswerValidatorTest.java b/backend/src/test/java/reviewme/review/service/validator/AnswerValidatorTest.java
new file mode 100644
index 000000000..16d528f0b
--- /dev/null
+++ b/backend/src/test/java/reviewme/review/service/validator/AnswerValidatorTest.java
@@ -0,0 +1,99 @@
+package reviewme.review.service.validator;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
+import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
+import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import reviewme.review.domain.Answer;
+import reviewme.review.domain.Review;
+import reviewme.review.domain.TextAnswer;
+import reviewme.review.repository.ReviewRepository;
+import reviewme.review.service.exception.QuestionNotContainingAnswersException;
+import reviewme.review.service.exception.ReviewGroupNotContainingAnswersException;
+import reviewme.reviewgroup.domain.ReviewGroup;
+import reviewme.reviewgroup.repository.ReviewGroupRepository;
+import reviewme.support.ServiceTest;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
+import reviewme.template.repository.TemplateRepository;
+
+@ServiceTest
+class AnswerValidatorTest {
+
+ @Autowired
+ private AnswerValidator answerValidator;
+
+ @Autowired
+ private ReviewGroupRepository reviewGroupRepository;
+
+ @Autowired
+ private ReviewRepository reviewRepository;
+
+ @Autowired
+ private TemplateRepository templateRepository;
+
+ @Test
+ void 답변이_질문에_속하는지_검증한다() {
+ // given
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ Question question1 = 서술형_필수_질문();
+ Question question2 = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question1, question2));
+ templateRepository.save(new Template(List.of(section)));
+
+ List answers = List.of(
+ new TextAnswer(question1.getId(), "답변1"),
+ new TextAnswer(question2.getId(), "답변2")
+ );
+ Review review = reviewRepository.save(new Review(1L, reviewGroup.getId(), answers));
+ Set answerIds = review.getAnsweredQuestionIds();
+ List firstAnswerId = List.of(answers.get(0).getId());
+
+ // when, then
+ assertAll(
+ () -> assertDoesNotThrow(
+ () -> answerValidator.validateQuestionContainsAnswers(question1.getId(), firstAnswerId)),
+ () -> assertThatThrownBy(
+ () -> answerValidator.validateQuestionContainsAnswers(question1.getId(), answerIds))
+ .isInstanceOf(QuestionNotContainingAnswersException.class)
+ );
+ }
+
+ @Test
+ void 답변이_리뷰그룹에_속하는지_검증한다() {
+ // given
+ ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ Question question1 = 서술형_필수_질문();
+ Question question2 = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question1, question2));
+ templateRepository.save(new Template(List.of(section)));
+
+ List answers = List.of(
+ new TextAnswer(question1.getId(), "답변1"),
+ new TextAnswer(question2.getId(), "답변2")
+ );
+ Review review = reviewRepository.save(new Review(1L, reviewGroup.getId(), answers));
+
+ List answerIds = review.getAnswers().stream().map(Answer::getQuestionId).toList();
+ List wrongAnswerIds = new ArrayList<>(answerIds);
+ wrongAnswerIds.add(Long.MAX_VALUE);
+
+ // when, then
+ assertAll(
+ () -> assertDoesNotThrow(
+ () -> answerValidator.validateReviewGroupContainsAnswers(reviewGroup, answerIds)),
+ () -> assertThatThrownBy(
+ () -> answerValidator.validateReviewGroupContainsAnswers(reviewGroup, wrongAnswerIds))
+ .isInstanceOf(ReviewGroupNotContainingAnswersException.class)
+ );
+ }
+}
diff --git a/backend/src/test/java/reviewme/review/service/validator/CheckboxAnswerValidatorTest.java b/backend/src/test/java/reviewme/review/service/validator/CheckboxTypedAnswerValidatorTest.java
similarity index 56%
rename from backend/src/test/java/reviewme/review/service/validator/CheckboxAnswerValidatorTest.java
rename to backend/src/test/java/reviewme/review/service/validator/CheckboxTypedAnswerValidatorTest.java
index 5c64c2503..4565fb02b 100644
--- a/backend/src/test/java/reviewme/review/service/validator/CheckboxAnswerValidatorTest.java
+++ b/backend/src/test/java/reviewme/review/service/validator/CheckboxTypedAnswerValidatorTest.java
@@ -4,37 +4,33 @@
import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
import static reviewme.fixture.OptionItemFixture.선택지;
import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
+import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.service.exception.CheckBoxAnswerIncludedNotProvidedOptionItemException;
import reviewme.review.service.exception.OptionGroupNotFoundByQuestionIdException;
import reviewme.review.service.exception.SelectedOptionItemCountOutOfRangeException;
import reviewme.review.service.exception.SubmittedQuestionNotFoundException;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
+import reviewme.template.repository.TemplateRepository;
@ServiceTest
-class CheckboxAnswerValidatorTest {
+class CheckboxTypedAnswerValidatorTest {
@Autowired
- private CheckboxAnswerValidator checkBoxAnswerValidator;
+ private CheckboxTypedAnswerValidator checkBoxAnswerValidator;
@Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
+ private TemplateRepository templateRepository;
@Test
void 저장되지_않은_질문에_대한_답변이면_예외가_발생한다() {
@@ -50,7 +46,10 @@ class CheckboxAnswerValidatorTest {
@Test
void 옵션_그룹이_지정되지_않은_질문에_대한_답변이면_예외가_발생한다() {
// given
- Question savedQuestion = questionRepository.save(선택형_필수_질문());
+ Question savedQuestion = 선택형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(savedQuestion));
+ templateRepository.save(new Template(List.of(section)));
+
CheckboxAnswer checkboxAnswer = new CheckboxAnswer(savedQuestion.getId(), List.of(1L));
// when, then
@@ -61,9 +60,11 @@ class CheckboxAnswerValidatorTest {
@Test
void 옵션그룹에서_제공하지_않은_옵션아이템을_응답하면_예외가_발생한다() {
// given
- Question savedQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup savedOptionGroup = optionGroupRepository.save(선택지_그룹(savedQuestion.getId()));
- OptionItem savedOptionItem = optionItemRepository.save(선택지(savedOptionGroup.getId()));
+ OptionItem savedOptionItem = 선택지();
+ OptionGroup savedOptionGroup = 선택지_그룹(List.of(savedOptionItem));
+ Question savedQuestion = new Question(true, QuestionType.CHECKBOX, savedOptionGroup, "질문", "설명", 1);
+ Section section = 항상_보이는_섹션(List.of(savedQuestion));
+ templateRepository.save(new Template(List.of(section)));
CheckboxAnswer checkboxAnswer = new CheckboxAnswer(savedQuestion.getId(),
List.of(savedOptionItem.getId() + 1L));
@@ -76,14 +77,17 @@ class CheckboxAnswerValidatorTest {
@Test
void 옵션그룹에서_정한_최소_선택_수_보다_적게_선택하면_예외가_발생한다() {
// given
- Question savedQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup savedOptionGroup = optionGroupRepository.save(
- new OptionGroup(savedQuestion.getId(), 2, 3)
+ OptionItem savedOptionItem1 = 선택지();
+ OptionItem savedOptionItem2 = 선택지();
+ OptionItem savedOptionItem3 = 선택지();
+ OptionGroup savedOptionGroup = new OptionGroup(
+ List.of(savedOptionItem1, savedOptionItem2, savedOptionItem3), 2, 3
);
- OptionItem savedOptionItem1 = optionItemRepository.save(선택지(savedOptionGroup.getId()));
+ Question savedQuestion = new Question(true, QuestionType.CHECKBOX, savedOptionGroup, "질문", "설명", 1);
+ Section section = 항상_보이는_섹션(List.of(savedQuestion));
+ templateRepository.save(new Template(List.of(section)));
- CheckboxAnswer checkboxAnswer = new CheckboxAnswer(savedQuestion.getId(),
- List.of(savedOptionItem1.getId()));
+ CheckboxAnswer checkboxAnswer = new CheckboxAnswer(savedQuestion.getId(), List.of(savedOptionItem1.getId()));
// when, then
assertThatCode(() -> checkBoxAnswerValidator.validate(checkboxAnswer))
@@ -93,15 +97,19 @@ class CheckboxAnswerValidatorTest {
@Test
void 옵션그룹에서_정한_최대_선택_수_보다_많이_선택하면_예외가_발생한다() {
// given
- Question savedQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup savedOptionGroup = optionGroupRepository.save(
- new OptionGroup(savedQuestion.getId(), 1, 1)
+ OptionItem savedOptionItem1 = 선택지();
+ OptionItem savedOptionItem2 = 선택지();
+ OptionItem savedOptionItem3 = 선택지();
+ OptionGroup savedOptionGroup = new OptionGroup(
+ List.of(savedOptionItem1, savedOptionItem2, savedOptionItem3), 1, 1
);
- OptionItem savedOptionItem1 = optionItemRepository.save(선택지(savedOptionGroup.getId(), 1));
- OptionItem savedOptionItem2 = optionItemRepository.save(선택지(savedOptionGroup.getId(), 2));
+ Question savedQuestion = new Question(true, QuestionType.CHECKBOX, savedOptionGroup, "질문", "설명", 1);
+ Section section = 항상_보이는_섹션(List.of(savedQuestion));
+ templateRepository.save(new Template(List.of(section)));
CheckboxAnswer checkboxAnswer = new CheckboxAnswer(
- savedQuestion.getId(), List.of(savedOptionItem1.getId(), savedOptionItem2.getId()));
+ savedQuestion.getId(), List.of(savedOptionItem1.getId(), savedOptionItem2.getId())
+ );
// when, then
assertThatCode(() -> checkBoxAnswerValidator.validate(checkboxAnswer))
diff --git a/backend/src/test/java/reviewme/review/service/validator/ReviewValidatorTest.java b/backend/src/test/java/reviewme/review/service/validator/ReviewValidatorTest.java
index dca5dd59d..df0e6ac72 100644
--- a/backend/src/test/java/reviewme/review/service/validator/ReviewValidatorTest.java
+++ b/backend/src/test/java/reviewme/review/service/validator/ReviewValidatorTest.java
@@ -6,21 +6,13 @@
import static reviewme.fixture.OptionItemFixture.선택지;
import static reviewme.fixture.QuestionFixture.서술형_옵션_질문;
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
import static reviewme.fixture.SectionFixture.조건부로_보이는_섹션;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.OptionItem;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.CheckboxAnswer;
import reviewme.review.domain.Review;
import reviewme.review.domain.TextAnswer;
@@ -29,32 +21,23 @@
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.OptionGroup;
+import reviewme.template.domain.OptionItem;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.QuestionType;
import reviewme.template.domain.Section;
import reviewme.template.domain.Template;
-import reviewme.template.repository.SectionRepository;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
class ReviewValidatorTest {
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
@Autowired
private ReviewGroupRepository reviewGroupRepository;
@Autowired
private TemplateRepository templateRepository;
- @Autowired
- private SectionRepository sectionRepository;
-
@Autowired
private ReviewValidator reviewValidator;
@@ -64,43 +47,40 @@ class ReviewValidatorTest {
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
// 필수가 아닌 서술형 질문 저장
- Question notRequiredTextQuestion = questionRepository.save(서술형_옵션_질문());
- Section visibleSection1 = sectionRepository.save(항상_보이는_섹션(List.of(notRequiredTextQuestion.getId()), 1));
+ Question notRequiredTextQuestion = 서술형_옵션_질문();
+ Section visibleSection1 = 항상_보이는_섹션(List.of(notRequiredTextQuestion), 1);
// 필수 선택형 질문, 섹션 저장
- Question requiredCheckQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup requiredOptionGroup = optionGroupRepository.save(선택지_그룹(requiredCheckQuestion.getId()));
- OptionItem requiredOptionItem1 = optionItemRepository.save(선택지(requiredOptionGroup.getId()));
- OptionItem requiredOptionItem2 = optionItemRepository.save(선택지(requiredOptionGroup.getId()));
- Section visibleSection2 = sectionRepository.save(항상_보이는_섹션(List.of(requiredCheckQuestion.getId()), 2));
+ OptionItem requiredOptionItem1 = 선택지();
+ OptionItem requiredOptionItem2 = 선택지();
+ OptionGroup requiredOptionGroup = 선택지_그룹(List.of(requiredOptionItem1, requiredOptionItem2));
+ Question requiredCheckQuestion = new Question(true, QuestionType.CHECKBOX, requiredOptionGroup, "질문", "설명", 1);
+ Section visibleSection2 = 항상_보이는_섹션(List.of(requiredCheckQuestion), 2);
// optionItem 선택에 따라서 required 가 달라지는 섹션1 저장
- Question conditionalTextQuestion1 = questionRepository.save(서술형_필수_질문());
- Question conditionalCheckQuestion = questionRepository.save(선택형_필수_질문());
- OptionGroup conditionalOptionGroup = optionGroupRepository.save(선택지_그룹(conditionalCheckQuestion.getId()));
- OptionItem conditionalOptionItem = optionItemRepository.save(선택지(conditionalOptionGroup.getId()));
- Section conditionalSection1 = sectionRepository.save(조건부로_보이는_섹션(
- List.of(conditionalTextQuestion1.getId(), conditionalCheckQuestion.getId()),
- requiredOptionItem1.getId(), 3)
+ OptionItem conditionalOptionItem = 선택지();
+ OptionGroup conditionalOptionGroup = 선택지_그룹(List.of(conditionalOptionItem));
+ Question conditionalTextQuestion = 서술형_필수_질문();
+ Question conditionalCheckQuestion = new Question(true, QuestionType.CHECKBOX, conditionalOptionGroup, "질문",
+ "설명", 1);
+ Section conditionalSection1 = 조건부로_보이는_섹션(
+ List.of(conditionalTextQuestion, conditionalCheckQuestion), requiredOptionItem1, 3
);
// optionItem 선택에 따라서 required 가 달라지는 섹션2 저장
- Question conditionalQuestion2 = questionRepository.save(서술형_필수_질문());
- Section conditionalSection2 = sectionRepository.save(조건부로_보이는_섹션(
- List.of(conditionalQuestion2.getId()), requiredOptionItem2.getId(), 3)
- );
+ Question conditionalQuestion2 = 서술형_필수_질문();
+ Section conditionalSection2 = 조건부로_보이는_섹션(List.of(conditionalQuestion2), requiredOptionItem2, 3);
// 템플릿 저장
- Template template = templateRepository.save(템플릿(
- List.of(visibleSection1.getId(), visibleSection2.getId(),
- conditionalSection1.getId(), conditionalSection2.getId())
- ));
+ Template template = templateRepository.save(
+ new Template(List.of(visibleSection1, visibleSection2, conditionalSection1, conditionalSection2))
+ );
// 각 질문에 대한 답변 생성
TextAnswer notRequiredTextAnswer = new TextAnswer(notRequiredTextQuestion.getId(), "답변".repeat(30));
CheckboxAnswer alwaysRequiredCheckAnswer = new CheckboxAnswer(requiredCheckQuestion.getId(),
List.of(requiredOptionItem1.getId()));
- TextAnswer conditionalTextAnswer1 = new TextAnswer(conditionalTextQuestion1.getId(), "답변".repeat(30));
+ TextAnswer conditionalTextAnswer1 = new TextAnswer(conditionalTextQuestion.getId(), "답변".repeat(30));
CheckboxAnswer conditionalCheckAnswer1 = new CheckboxAnswer(conditionalCheckQuestion.getId(),
List.of(conditionalOptionItem.getId()));
@@ -119,10 +99,15 @@ class ReviewValidatorTest {
// given
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- Question question1 = questionRepository.save(서술형_필수_질문());
- Question question2 = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ // 재공된 템플릿
+ Question question1 = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question1));
+ Template template = templateRepository.save(new Template(List.of(section)));
+
+ // 다른 템플릿
+ Question question2 = 서술형_필수_질문();
+ Section section2 = 항상_보이는_섹션(List.of(question2));
+ templateRepository.save(new Template(List.of(section2)));
TextAnswer textAnswer = new TextAnswer(question2.getId(), "답변".repeat(20));
Review review = new Review(template.getId(), reviewGroup.getId(), List.of(textAnswer));
@@ -137,11 +122,10 @@ class ReviewValidatorTest {
// given
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
- Question requiredQuestion = questionRepository.save(서술형_필수_질문());
- Question optionalQuestion = questionRepository.save(서술형_옵션_질문());
- Section section = sectionRepository.save(
- 항상_보이는_섹션(List.of(requiredQuestion.getId(), optionalQuestion.getId())));
- Template template = templateRepository.save(템플릿(List.of(section.getId())));
+ Question requiredQuestion = 서술형_필수_질문();
+ Question optionalQuestion = 서술형_옵션_질문();
+ Section section = 항상_보이는_섹션(List.of(requiredQuestion, optionalQuestion));
+ Template template = templateRepository.save(new Template(List.of(section)));
TextAnswer optionalTextAnswer = new TextAnswer(optionalQuestion.getId(), "답변".repeat(20));
Review review = new Review(template.getId(), reviewGroup.getId(), List.of(optionalTextAnswer));
diff --git a/backend/src/test/java/reviewme/review/service/validator/TextAnswerValidatorTest.java b/backend/src/test/java/reviewme/review/service/validator/TextTypedAnswerValidatorTest.java
similarity index 67%
rename from backend/src/test/java/reviewme/review/service/validator/TextAnswerValidatorTest.java
rename to backend/src/test/java/reviewme/review/service/validator/TextTypedAnswerValidatorTest.java
index 0e8265bb6..ea42c2cab 100644
--- a/backend/src/test/java/reviewme/review/service/validator/TextAnswerValidatorTest.java
+++ b/backend/src/test/java/reviewme/review/service/validator/TextTypedAnswerValidatorTest.java
@@ -4,27 +4,32 @@
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static reviewme.fixture.QuestionFixture.서술형_옵션_질문;
import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
+import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
+import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.QuestionRepository;
import reviewme.review.domain.TextAnswer;
import reviewme.review.service.exception.InvalidTextAnswerLengthException;
import reviewme.review.service.exception.SubmittedQuestionNotFoundException;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
+import reviewme.template.repository.TemplateRepository;
@ServiceTest
-class TextAnswerValidatorTest {
+class TextTypedAnswerValidatorTest {
@Autowired
- private TextAnswerValidator textAnswerValidator;
+ private TextTypedAnswerValidator textAnswerValidator;
@Autowired
- private QuestionRepository questionRepository;
+ private TemplateRepository templateRepository;
+ // TODO: Answer의 검증을 위해 Template의 저장이 필요하다. (Question의 제한), 조금 더 위쪽 레이어에서 명세를 활용해서 처리해도 좋겠다.
@Test
void 저장되지_않은_질문에_대한_대답이면_예외가_발생한다() {
// given
@@ -41,7 +46,10 @@ class TextAnswerValidatorTest {
void 필수_질문의_답변_길이가_유효하지_않으면_예외가_발생한다(int length) {
// given
String content = "답".repeat(length);
- Question savedQuestion = questionRepository.save(서술형_필수_질문());
+ Question savedQuestion = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(savedQuestion));
+ templateRepository.save(new Template(List.of(section)));
+
TextAnswer textAnswer = new TextAnswer(savedQuestion.getId(), content);
// when, then
@@ -53,7 +61,10 @@ class TextAnswerValidatorTest {
void 선택_질문의_답변_길이가_유효하지_않으면_예외가_발생한다() {
// given
String content = "답".repeat(10001);
- Question savedQuestion = questionRepository.save(서술형_옵션_질문());
+ Question savedQuestion = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(savedQuestion));
+ templateRepository.save(new Template(List.of(section)));
+
TextAnswer textAnswer = new TextAnswer(savedQuestion.getId(), content);
// when, then
@@ -65,7 +76,10 @@ class TextAnswerValidatorTest {
void 선택_질문은_최소_글자수_제한을_받지_않는다() {
// given
String content = "답".repeat(1);
- Question savedQuestion = questionRepository.save(서술형_옵션_질문());
+ Question savedQuestion = 서술형_옵션_질문();
+ Section section = 항상_보이는_섹션(List.of(savedQuestion));
+ templateRepository.save(new Template(List.of(section)));
+
TextAnswer textAnswer = new TextAnswer(savedQuestion.getId(), content);
// when, then
diff --git a/backend/src/test/java/reviewme/review/service/validator/AnswerValidatorFactoryTest.java b/backend/src/test/java/reviewme/review/service/validator/TypedTypedAnswerValidatorFactoryTest.java
similarity index 68%
rename from backend/src/test/java/reviewme/review/service/validator/AnswerValidatorFactoryTest.java
rename to backend/src/test/java/reviewme/review/service/validator/TypedTypedAnswerValidatorFactoryTest.java
index 0a6e75db5..d808c53d3 100644
--- a/backend/src/test/java/reviewme/review/service/validator/AnswerValidatorFactoryTest.java
+++ b/backend/src/test/java/reviewme/review/service/validator/TypedTypedAnswerValidatorFactoryTest.java
@@ -8,9 +8,9 @@
import reviewme.review.domain.Answer;
import reviewme.review.domain.CheckboxAnswer;
-class AnswerValidatorFactoryTest {
+class TypedTypedAnswerValidatorFactoryTest {
- private final AnswerValidator validator = new AnswerValidator() {
+ private final TypedAnswerValidator validator = new TypedAnswerValidator() {
@Override
public boolean supports(Class extends Answer> answerClass) {
@@ -19,17 +19,18 @@ public boolean supports(Class extends Answer> answerClass) {
@Override
public void validate(Answer answer) {
+ // no-op
}
};
@Test
void 지원하는_타입에_따른_밸리데이터를_가져온다() {
// given
- List validators = List.of(validator);
- AnswerValidatorFactory factory = new AnswerValidatorFactory(validators);
+ List validators = List.of(validator);
+ TypedAnswerValidatorFactory factory = new TypedAnswerValidatorFactory(validators);
// when
- AnswerValidator actual = factory.getAnswerValidator(CheckboxAnswer.class);
+ TypedAnswerValidator actual = factory.getAnswerValidator(CheckboxAnswer.class);
// then
assertThat(actual).isEqualTo(validator);
@@ -38,7 +39,7 @@ public void validate(Answer answer) {
@Test
void 지원하지_않는_타입에_대한_밸리데이터_요청_시_예외가_발생한다() {
// given
- AnswerValidatorFactory factory = new AnswerValidatorFactory(List.of());
+ TypedAnswerValidatorFactory factory = new TypedAnswerValidatorFactory(List.of());
// when, then
assertThatThrownBy(() -> factory.getAnswerValidator(CheckboxAnswer.class))
diff --git a/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupLookupServiceTest.java b/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupLookupServiceTest.java
index a7719e52f..4c553a9a3 100644
--- a/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupLookupServiceTest.java
+++ b/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupLookupServiceTest.java
@@ -6,7 +6,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.reviewgroup.service.dto.ReviewGroupResponse;
diff --git a/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupServiceTest.java b/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupServiceTest.java
index 6bdc22f44..af46b2070 100644
--- a/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupServiceTest.java
+++ b/backend/src/test/java/reviewme/reviewgroup/service/ReviewGroupServiceTest.java
@@ -8,8 +8,9 @@
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.times;
+import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
-import static reviewme.fixture.TemplateFixture.템플릿;
+import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
import java.util.List;
import org.junit.jupiter.api.Test;
@@ -17,14 +18,17 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
-import reviewme.review.service.exception.ReviewGroupUnauthorizedException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.reviewgroup.service.dto.CheckValidAccessRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationRequest;
import reviewme.reviewgroup.service.dto.ReviewGroupCreationResponse;
+import reviewme.reviewgroup.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
+import reviewme.reviewgroup.service.exception.ReviewGroupUnauthorizedException;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
import reviewme.template.repository.TemplateRepository;
@ServiceTest
@@ -46,7 +50,10 @@ class ReviewGroupServiceTest {
@Test
void 코드가_중복되는_경우_다시_생성한다() {
// given
- templateRepository.save(템플릿(List.of()));
+ Question question = 서술형_필수_질문();
+ Section section = 항상_보이는_섹션(List.of(question));
+ templateRepository.save(new Template(List.of(section)));
+
reviewGroupRepository.save(리뷰_그룹("0000", "1111"));
given(randomCodeGenerator.generate(anyInt()))
.willReturn("0000") // ReviewRequestCode
diff --git a/backend/src/test/java/reviewme/support/CacheCleaner.java b/backend/src/test/java/reviewme/support/CacheCleaner.java
new file mode 100644
index 000000000..7c96acfe6
--- /dev/null
+++ b/backend/src/test/java/reviewme/support/CacheCleaner.java
@@ -0,0 +1,22 @@
+package reviewme.support;
+
+import java.util.Objects;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+public class CacheCleaner {
+
+ private final CacheManager cacheManager;
+
+ public CacheCleaner(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ public void execute() {
+ cacheManager.getCacheNames()
+ .stream()
+ .map(cacheManager::getCache)
+ .filter(Objects::nonNull)
+ .forEach(Cache::clear);
+ }
+}
diff --git a/backend/src/test/java/reviewme/support/CacheCleanerExtension.java b/backend/src/test/java/reviewme/support/CacheCleanerExtension.java
new file mode 100644
index 000000000..e941b6e83
--- /dev/null
+++ b/backend/src/test/java/reviewme/support/CacheCleanerExtension.java
@@ -0,0 +1,15 @@
+package reviewme.support;
+
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+public class CacheCleanerExtension implements BeforeEachCallback {
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) {
+ SpringExtension.getApplicationContext(extensionContext)
+ .getBean(CacheCleaner.class)
+ .execute();
+ }
+}
diff --git a/backend/src/test/java/reviewme/support/ServiceTest.java b/backend/src/test/java/reviewme/support/ServiceTest.java
index 34ae4b4fd..c3838a35f 100644
--- a/backend/src/test/java/reviewme/support/ServiceTest.java
+++ b/backend/src/test/java/reviewme/support/ServiceTest.java
@@ -12,6 +12,6 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@SpringBootTest(webEnvironment = WebEnvironment.NONE, classes = TestConfig.class)
-@ExtendWith(DatabaseCleanerExtension.class)
+@ExtendWith({DatabaseCleanerExtension.class, CacheCleanerExtension.class})
public @interface ServiceTest {
}
diff --git a/backend/src/test/java/reviewme/template/domain/SectionTest.java b/backend/src/test/java/reviewme/template/domain/SectionTest.java
index 0e3f1339b..84db41a5b 100644
--- a/backend/src/test/java/reviewme/template/domain/SectionTest.java
+++ b/backend/src/test/java/reviewme/template/domain/SectionTest.java
@@ -1,58 +1,45 @@
package reviewme.template.domain;
import static org.assertj.core.api.Assertions.assertThat;
-import static reviewme.fixture.SectionFixture.조건부로_보이는_섹션;
import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
import java.util.List;
import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
class SectionTest {
@Test
void 조건_옵션을_선택하면_섹션이_보인다() {
// given
- List questionIds = List.of(1L);
- long optionId1 = 1L;
- long optionId2 = 2L;
- long optionId3 = 3L;
+ OptionItem optionItem = new OptionItem("content", 1, OptionType.CATEGORY);
+ ReflectionTestUtils.setField(optionItem, "id", 1L);
+ Question question = new Question(true, QuestionType.CHECKBOX, "question", null, 1);
+ Section section = new Section(VisibleType.CONDITIONAL, List.of(question), optionItem, "name", "header", 1);
- Section section = 조건부로_보이는_섹션(questionIds, optionId2);
-
- // when
- boolean actual = section.isVisibleBySelectedOptionIds(List.of(optionId1, optionId2, optionId3));
-
- // then
- assertThat(actual).isTrue();
+ // when, then
+ assertThat(section.isVisibleBySelectedOptionIds(List.of(1L, 2L, 3L))).isTrue();
}
@Test
void 조건_옵션을_선택하지_않으면_섹션이_보이지_않는다() {
// given
- List questionIds = List.of(1L);
- long optionId1 = 1L;
- long optionId2 = 2L;
- long optionId3 = 3L;
-
- Section section = 조건부로_보이는_섹션(questionIds, optionId2);
+ OptionItem optionItem = new OptionItem("content", 1, OptionType.CATEGORY);
+ ReflectionTestUtils.setField(optionItem, "id", 1L);
+ Question question = new Question(true, QuestionType.CHECKBOX, "question", null, 1);
+ Section section = new Section(VisibleType.CONDITIONAL, List.of(question), optionItem, "name", "header", 1);
- // when
- boolean actual = section.isVisibleBySelectedOptionIds(List.of(optionId1, optionId3));
-
- // then
- assertThat(actual).isFalse();
+ // when, then
+ assertThat(section.isVisibleBySelectedOptionIds(List.of(2L))).isFalse();
}
@Test
void 타입이_ALWAYS라면_조건과_상관없이_모두_보인다() {
// given
- List questionIds = List.of(1L);
- Section section = 항상_보이는_섹션(questionIds);
-
- // when
- boolean actual = section.isVisibleBySelectedOptionIds(List.of());
+ Question question = new Question(true, QuestionType.CHECKBOX, "question", null, 1);
+ Section section = 항상_보이는_섹션(List.of(question));
- // then
- assertThat(actual).isTrue();
+ // when, then
+ assertThat(section.isVisibleBySelectedOptionIds(List.of())).isTrue();
}
}
diff --git a/backend/src/test/java/reviewme/template/repository/SectionRepositoryTest.java b/backend/src/test/java/reviewme/template/repository/SectionRepositoryTest.java
deleted file mode 100644
index 41e2699ed..000000000
--- a/backend/src/test/java/reviewme/template/repository/SectionRepositoryTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package reviewme.template.repository;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
-
-import java.util.List;
-import java.util.Optional;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.Template;
-
-@DataJpaTest
-class SectionRepositoryTest {
-
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private TemplateRepository templateRepository;
-
- @Test
- void 템플릿_아이디로_섹션을_불러온다() {
- // given
- List questionIds = List.of(1L);
- Section section1 = sectionRepository.save(항상_보이는_섹션(questionIds));
- Section section2 = sectionRepository.save(항상_보이는_섹션(questionIds));
- Section section3 = sectionRepository.save(항상_보이는_섹션(questionIds));
- sectionRepository.save(항상_보이는_섹션(questionIds));
- List sectionIds = List.of(section1.getId(), section2.getId(), section3.getId());
-
- Template template = templateRepository.save(템플릿(sectionIds));
-
- // when
- List actual = sectionRepository.findAllByTemplateId(template.getId());
-
- // then
- assertThat(actual).containsExactly(section1, section2, section3);
- }
-
- @Test
- void 템플릿_아이디와_섹션_아이디에_해당하는_섹션을_반환한다() {
- // given
- List questionIds = List.of(1L);
- Section section1 = sectionRepository.save(항상_보이는_섹션(questionIds));
- Section section2 = sectionRepository.save(항상_보이는_섹션(questionIds));
- Template template = templateRepository.save(템플릿(List.of(section1.getId())));
-
- // when
- Optional actual1 = sectionRepository.findByIdAndTemplateId(section1.getId(), template.getId());
- Optional actual2 = sectionRepository.findByIdAndTemplateId(section2.getId(), template.getId());
-
- // then
- assertAll(
- () -> assertThat(actual1).isPresent(),
- () -> assertThat(actual2).isEmpty()
- );
- }
-}
diff --git a/backend/src/test/java/reviewme/template/service/SectionServiceTest.java b/backend/src/test/java/reviewme/template/service/SectionServiceTest.java
deleted file mode 100644
index c7e319d99..000000000
--- a/backend/src/test/java/reviewme/template/service/SectionServiceTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package reviewme.template.service;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
-import static reviewme.fixture.TemplateFixture.템플릿;
-
-import java.util.List;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.reviewgroup.repository.ReviewGroupRepository;
-import reviewme.support.ServiceTest;
-import reviewme.template.domain.Section;
-import reviewme.template.domain.VisibleType;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
-import reviewme.template.service.dto.response.SectionNameResponse;
-import reviewme.template.service.dto.response.SectionNamesResponse;
-
-@ServiceTest
-class SectionServiceTest {
-
- @Autowired
- private SectionService sectionService;
-
- @Autowired
- private ReviewGroupRepository reviewGroupRepository;
-
- @Autowired
- private TemplateRepository templateRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
- @Test
- void 템플릿에_있는_섹션_이름_목록을_응답한다() {
- // given
- String sectionName1 = "섹션1";
- String sectionName2 = "섹션2";
- String sectionName3 = "섹션3";
-
- Section visibleSection1 = sectionRepository.save(
- new Section(VisibleType.ALWAYS, List.of(1L), null, sectionName1, "헤더", 1));
- Section visibleSection2 = sectionRepository.save(
- new Section(VisibleType.ALWAYS, List.of(2L), null, sectionName2, "헤더", 2));
- Section nonVisibleSection = sectionRepository.save(
- new Section(VisibleType.CONDITIONAL, List.of(1L), 1L, sectionName3, "헤더", 3));
- templateRepository.save(
- 템플릿(List.of(nonVisibleSection.getId(), visibleSection2.getId(), visibleSection1.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- // when
- SectionNamesResponse actual = sectionService.getSectionNames(reviewGroup);
-
- // then
- assertThat(actual.sections()).extracting(SectionNameResponse::name)
- .containsExactly(sectionName1, sectionName2, sectionName3);
- }
-}
diff --git a/backend/src/test/java/reviewme/template/service/TemplateServiceTest.java b/backend/src/test/java/reviewme/template/service/TemplateServiceTest.java
index 7c512d99f..269964705 100644
--- a/backend/src/test/java/reviewme/template/service/TemplateServiceTest.java
+++ b/backend/src/test/java/reviewme/template/service/TemplateServiceTest.java
@@ -1,14 +1,24 @@
package reviewme.template.service;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
+import static reviewme.fixture.ReviewGroupFixture.템플릿_지정_리뷰_그룹;
+import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.review.service.exception.ReviewGroupNotFoundByReviewRequestCodeException;
import reviewme.reviewgroup.domain.ReviewGroup;
import reviewme.reviewgroup.repository.ReviewGroupRepository;
import reviewme.support.ServiceTest;
+import reviewme.template.domain.Question;
+import reviewme.template.domain.Section;
+import reviewme.template.domain.Template;
+import reviewme.template.domain.VisibleType;
+import reviewme.template.repository.TemplateRepository;
+import reviewme.template.service.dto.response.SectionNameResponse;
+import reviewme.template.service.dto.response.SectionNamesResponse;
import reviewme.template.service.exception.TemplateNotFoundByReviewGroupException;
@ServiceTest
@@ -20,23 +30,36 @@ class TemplateServiceTest {
@Autowired
private ReviewGroupRepository reviewGroupRepository;
+ @Autowired
+ private TemplateRepository templateRepository;
+
@Test
- void 잘못된_리뷰_요청_코드로_리뷰_작성폼을_요청할_경우_예외가_발생한다() {
+ void 리뷰이에게_작성될_리뷰_양식_생성_시_저장된_템플릿이_없을_경우_예외가_발생한다() {
// given
ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ String reviewRequestCode = reviewGroup.getReviewRequestCode();
// when, then
- assertThatThrownBy(() -> templateService.generateReviewForm(reviewGroup.getReviewRequestCode() + " "))
- .isInstanceOf(ReviewGroupNotFoundByReviewRequestCodeException.class);
+ assertThatThrownBy(() -> templateService.generateReviewForm(reviewRequestCode))
+ .isInstanceOf(TemplateNotFoundByReviewGroupException.class);
}
@Test
- void 리뷰이에게_작성될_리뷰_양식_생성_시_저장된_템플릿이_없을_경우_예외가_발생한다() {
+ void 템플릿에_있는_섹션_이름_목록을_응답한다() {
// given
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
+ Question question1 = 서술형_필수_질문(1);
+ Question question2 = 서술형_필수_질문(1);
- // when, then
- assertThatThrownBy(() -> templateService.generateReviewForm(reviewGroup.getReviewRequestCode()))
- .isInstanceOf(TemplateNotFoundByReviewGroupException.class);
+ Section section1 = new Section(VisibleType.ALWAYS, List.of(question1), null, "섹션1", "헤더", 1);
+ Section section2 = new Section(VisibleType.ALWAYS, List.of(question2), null, "섹션2", "헤더", 2);
+ Template template = templateRepository.save(new Template(List.of(section1, section2)));
+ ReviewGroup reviewGroup = reviewGroupRepository.save(템플릿_지정_리뷰_그룹(template.getId()));
+
+ // when
+ SectionNamesResponse actual = templateService.getSectionNames(reviewGroup);
+
+ // then
+ assertThat(actual.sections()).extracting(SectionNameResponse::name)
+ .containsExactly("섹션1", "섹션2");
}
}
diff --git a/backend/src/test/java/reviewme/template/service/mapper/TemplateMapperTest.java b/backend/src/test/java/reviewme/template/service/mapper/TemplateMapperTest.java
deleted file mode 100644
index e1a0f1853..000000000
--- a/backend/src/test/java/reviewme/template/service/mapper/TemplateMapperTest.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package reviewme.template.service.mapper;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static reviewme.fixture.OptionGroupFixture.선택지_그룹;
-import static reviewme.fixture.OptionItemFixture.선택지;
-import static reviewme.fixture.QuestionFixture.서술형_필수_질문;
-import static reviewme.fixture.QuestionFixture.선택형_필수_질문;
-import static reviewme.fixture.ReviewGroupFixture.리뷰_그룹;
-import static reviewme.fixture.SectionFixture.항상_보이는_섹션;
-import static reviewme.fixture.TemplateFixture.템플릿;
-
-import java.util.List;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import reviewme.question.domain.OptionGroup;
-import reviewme.question.domain.Question;
-import reviewme.question.repository.OptionGroupRepository;
-import reviewme.question.repository.OptionItemRepository;
-import reviewme.question.repository.QuestionRepository;
-import reviewme.reviewgroup.domain.ReviewGroup;
-import reviewme.reviewgroup.repository.ReviewGroupRepository;
-import reviewme.support.ServiceTest;
-import reviewme.template.domain.Section;
-import reviewme.template.service.exception.MissingOptionItemsInOptionGroupException;
-import reviewme.template.service.exception.SectionInTemplateNotFoundException;
-import reviewme.template.repository.SectionRepository;
-import reviewme.template.repository.TemplateRepository;
-import reviewme.template.service.dto.response.QuestionResponse;
-import reviewme.template.service.dto.response.SectionResponse;
-import reviewme.template.service.dto.response.TemplateResponse;
-
-@ServiceTest
-class TemplateMapperTest {
-
- @Autowired
- private TemplateMapper templateMapper;
-
- @Autowired
- private TemplateRepository templateRepository;
-
- @Autowired
- private SectionRepository sectionRepository;
-
- @Autowired
- private QuestionRepository questionRepository;
-
- @Autowired
- private OptionGroupRepository optionGroupRepository;
-
- @Autowired
- private OptionItemRepository optionItemRepository;
-
- @Autowired
- private ReviewGroupRepository reviewGroupRepository;
-
- @Test
- void 리뷰_그룹과_템플릿으로_템플릿_응답을_매핑한다() {
- // given
- Question question1 = questionRepository.save(서술형_필수_질문());
- Question question2 = questionRepository.save(서술형_필수_질문());
-
- OptionGroup optionGroup = optionGroupRepository.save(선택지_그룹(question1.getId()));
- optionItemRepository.save(선택지(optionGroup.getId()));
-
- Section section1 = sectionRepository.save(항상_보이는_섹션(List.of(question1.getId())));
- Section section2 = sectionRepository.save(항상_보이는_섹션(List.of(question2.getId())));
-
- templateRepository.save(템플릿(List.of(section1.getId(), section2.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- // when
- TemplateResponse templateResponse = templateMapper.mapToTemplateResponse(reviewGroup);
-
- // then
- assertAll(
- () -> assertThat(templateResponse.revieweeName()).isEqualTo(reviewGroup.getReviewee()),
- () -> assertThat(templateResponse.projectName()).isEqualTo(reviewGroup.getProjectName()),
- () -> assertThat(templateResponse.sections()).hasSize(2),
- () -> assertThat(templateResponse.sections().get(0).header()).isEqualTo(section1.getHeader()),
- () -> assertThat(templateResponse.sections().get(0).questions()).hasSize(1),
- () -> assertThat(templateResponse.sections().get(1).header()).isEqualTo(section2.getHeader()),
- () -> assertThat(templateResponse.sections().get(1).questions()).hasSize(1)
- );
- }
-
- @Test
- void 섹션의_선택된_옵션이_필요없는_경우_제공하지_않는다() {
- // given
- Question question = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- // when
- TemplateResponse templateResponse = templateMapper.mapToTemplateResponse(reviewGroup);
-
- // then
- SectionResponse sectionResponse = templateResponse.sections().get(0);
- assertThat(sectionResponse.onSelectedOptionId()).isNull();
- }
-
- @Test
- void 가이드라인이_없는_경우_가이드_라인을_제공하지_않는다() {
- // given
- Question question = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- // when
- TemplateResponse templateResponse = templateMapper.mapToTemplateResponse(reviewGroup);
-
- // then
- QuestionResponse questionResponse = templateResponse.sections().get(0).questions().get(0);
- assertAll(
- () -> assertThat(questionResponse.hasGuideline()).isFalse(),
- () -> assertThat(questionResponse.guideline()).isNull()
- );
- }
-
- @Test
- void 옵션_그룹이_없는_질문의_경우_옵션_그룹을_제공하지_않는다() {
- // given
- Question question = questionRepository.save(서술형_필수_질문());
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- // when
- TemplateResponse templateResponse = templateMapper.mapToTemplateResponse(reviewGroup);
-
- // then
- QuestionResponse questionResponse = templateResponse.sections().get(0).questions().get(0);
- assertThat(questionResponse.optionGroup()).isNull();
- }
-
- @Test
- void 템플릿_매핑_시_템플릿에_제공할_섹션이_없을_경우_예외가_발생한다() {
- // given
- templateRepository.save(템플릿(List.of(1L)));
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- // when, then
- assertThatThrownBy(() -> templateMapper.mapToTemplateResponse(reviewGroup))
- .isInstanceOf(SectionInTemplateNotFoundException.class);
- }
-
- @Test
- void 템플릿_매핑_시_옵션_그룹에_해당하는_옵션_아이템이_없을_경우_예외가_발생한다() {
- // given
- Question question = questionRepository.save(선택형_필수_질문());
- optionGroupRepository.save(선택지_그룹(question.getId()));
-
- Section section = sectionRepository.save(항상_보이는_섹션(List.of(question.getId())));
- templateRepository.save(템플릿(List.of(section.getId())));
-
- ReviewGroup reviewGroup = reviewGroupRepository.save(리뷰_그룹());
-
- // when, then
- assertThatThrownBy(() -> templateMapper.mapToTemplateResponse(reviewGroup))
- .isInstanceOf(MissingOptionItemsInOptionGroupException.class);
- }
-}
diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml
index f18542246..541b9ff06 100644
--- a/backend/src/test/resources/application.yml
+++ b/backend/src/test/resources/application.yml
@@ -13,6 +13,8 @@ spring:
ddl-auto: update
flyway:
enabled: false
+ cache:
+ type: simple
springdoc:
swagger-ui:
@@ -38,9 +40,3 @@ logging:
cors:
allowed-origins:
- https://allowed-domain.com
-
-request-limit:
- threshold: 3
- duration: 1s
- host: localhost
- port: 6379
diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs
index dd825e3f4..ea0dcdbdb 100644
--- a/frontend/.eslintrc.cjs
+++ b/frontend/.eslintrc.cjs
@@ -26,6 +26,17 @@ module.exports = {
rules: {
'react/react-in-jsx-scope': 'off',
'react/no-unknown-property': ['error', { ignore: ['css'] }],
+ '@typescript-eslint/naming-convention': [
+ 'error',
+ {
+ selector: 'interface',
+ format: ['PascalCase'],
+ },
+ {
+ selector: 'typeAlias',
+ format: ['PascalCase'],
+ },
+ ],
'import/order': [
'error',
{
diff --git a/frontend/src/apis/group.ts b/frontend/src/apis/group.ts
index 2f7c2ed7e..6cc3374c6 100644
--- a/frontend/src/apis/group.ts
+++ b/frontend/src/apis/group.ts
@@ -1,4 +1,4 @@
-import { INVALID_REVIEW_PASSWORD_MESSAGE } from '@/constants';
+import { ERROR_BOUNDARY_IGNORE_ERROR, INVALID_REVIEW_PASSWORD_MESSAGE } from '@/constants';
import { PasswordResponse, ReviewGroupData } from '@/types';
import createApiErrorMessage from './apiErrorMessageCreator';
@@ -7,7 +7,7 @@ import endPoint from './endpoints';
export interface DataForReviewRequestCode {
revieweeName: string;
projectName: string;
- groupAccessCode: string;
+ groupAccessCode?: string;
}
export const postDataForReviewRequestCodeApi = async (dataForReviewRequestCode: DataForReviewRequestCode) => {
@@ -16,11 +16,12 @@ export const postDataForReviewRequestCodeApi = async (dataForReviewRequestCode:
headers: {
'Content-Type': 'application/json',
},
+ // TODO : 회원 리뷰 링크 API 문서 나오면 비밀번호 관련해 변경해야함
body: JSON.stringify(dataForReviewRequestCode),
});
if (!response.ok) {
- throw new Error(createApiErrorMessage(response.status));
+ throw new Error(`${createApiErrorMessage(response.status)} ${ERROR_BOUNDARY_IGNORE_ERROR}`);
}
const data = await response.json();
diff --git a/frontend/src/assets/backButton.svg b/frontend/src/assets/backButton.svg
new file mode 100644
index 000000000..3693f7b99
--- /dev/null
+++ b/frontend/src/assets/backButton.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/emptyContentIcon.svg b/frontend/src/assets/emptyContentIcon.svg
new file mode 100644
index 000000000..1c1d3e1d9
--- /dev/null
+++ b/frontend/src/assets/emptyContentIcon.svg
@@ -0,0 +1,12 @@
+
diff --git a/frontend/src/assets/github.svg b/frontend/src/assets/github.svg
new file mode 100644
index 000000000..518b64d8a
--- /dev/null
+++ b/frontend/src/assets/github.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/githubWhiteLogo.svg b/frontend/src/assets/githubWhiteLogo.svg
new file mode 100644
index 000000000..dc79940e2
--- /dev/null
+++ b/frontend/src/assets/githubWhiteLogo.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/assets/logout.svg b/frontend/src/assets/logout.svg
new file mode 100644
index 000000000..4cb4c48a4
--- /dev/null
+++ b/frontend/src/assets/logout.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/menu.svg b/frontend/src/assets/menu.svg
new file mode 100644
index 000000000..8c279f0a2
--- /dev/null
+++ b/frontend/src/assets/menu.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/openedBook.svg b/frontend/src/assets/openedBook.svg
new file mode 100644
index 000000000..233c9d6fe
--- /dev/null
+++ b/frontend/src/assets/openedBook.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/assets/slideArrows.svg b/frontend/src/assets/slideArrows.svg
new file mode 100644
index 000000000..acf1609d9
--- /dev/null
+++ b/frontend/src/assets/slideArrows.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/src/assets/user.svg b/frontend/src/assets/user.svg
new file mode 100644
index 000000000..9a80b77a0
--- /dev/null
+++ b/frontend/src/assets/user.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/components/ReviewListItem/index.tsx b/frontend/src/components/ReviewListItem/index.tsx
new file mode 100644
index 000000000..75dacaf8f
--- /dev/null
+++ b/frontend/src/components/ReviewListItem/index.tsx
@@ -0,0 +1,13 @@
+// 임시 컴포넌트! 작성한 리뷰 확인 && 받은 리뷰 확인 아이템
+
+import * as S from './styles';
+
+interface ReviewListItemProps {
+ handleClick: () => void;
+}
+
+const ReviewListItem = ({ handleClick }: ReviewListItemProps) => {
+ return 리뷰 목록 아이템입니다;
+};
+
+export default ReviewListItem;
diff --git a/frontend/src/components/ReviewListItem/styles.ts b/frontend/src/components/ReviewListItem/styles.ts
new file mode 100644
index 000000000..4d1408aa0
--- /dev/null
+++ b/frontend/src/components/ReviewListItem/styles.ts
@@ -0,0 +1,31 @@
+import styled from '@emotion/styled';
+
+import media from '@/utils/media';
+
+export const ReviewListItem = styled.li`
+ display: flex;
+ flex-direction: column;
+
+ min-width: ${({ theme }) => theme.writtenReviewLayoutSize.largeMinWidth};
+ max-width: ${({ theme }) => theme.writtenReviewLayoutSize.largeMaxWidth};
+ min-height: 20rem;
+ max-height: 24rem;
+
+ border: 0.2rem solid ${({ theme }) => theme.colors.placeholder};
+ border-radius: ${({ theme }) => theme.borderRadius.basic};
+
+ ${media.medium} {
+ min-width: 62vw;
+ min-height: 18vh;
+ }
+
+ ${media.small} {
+ min-width: 65vw;
+ min-height: 14vh;
+ }
+
+ ${media.xSmall} {
+ min-width: 70vw;
+ min-height: 14vh;
+ }
+`;
diff --git a/frontend/src/components/common/Accordion/styles.ts b/frontend/src/components/common/Accordion/styles.ts
index b701f6c60..49a5a4836 100644
--- a/frontend/src/components/common/Accordion/styles.ts
+++ b/frontend/src/components/common/Accordion/styles.ts
@@ -24,7 +24,6 @@ export const AccordionContainer = styled.div`
export const AccordionHeader = styled.div`
display: flex;
- padding: 1rem;
border-bottom: ${({ $isOpened, theme }) => $isOpened && `0.1rem solid ${theme.colors.placeholder}`};
`;
@@ -36,7 +35,8 @@ export const AccordionButton = styled.button`
width: 100%;
height: fit-content;
- min-height: 3rem;
+ min-height: 5rem;
+ padding: 1rem;
`;
export const AccordionTitle = styled.p`
diff --git a/frontend/src/components/common/BackButton/index.tsx b/frontend/src/components/common/BackButton/index.tsx
new file mode 100644
index 000000000..c2e295c27
--- /dev/null
+++ b/frontend/src/components/common/BackButton/index.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import { NavigateOptions, useNavigate } from 'react-router';
+
+import BackButtonIcon from '@/assets/backButton.svg';
+
+import * as S from './styles';
+
+interface BackButtonProps {
+ prevPath: string;
+ navigateOptions?: NavigateOptions;
+ buttonStyle?: React.CSSProperties;
+ wrapperStyle?: React.CSSProperties;
+}
+
+const BackButton = ({ prevPath, navigateOptions, buttonStyle, wrapperStyle }: BackButtonProps) => {
+ const navigate = useNavigate();
+
+ const handleBackButtonClick = () => {
+ navigate(prevPath, navigateOptions);
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default BackButton;
diff --git a/frontend/src/components/common/BackButton/styles.ts b/frontend/src/components/common/BackButton/styles.ts
new file mode 100644
index 000000000..46a909b04
--- /dev/null
+++ b/frontend/src/components/common/BackButton/styles.ts
@@ -0,0 +1,20 @@
+import styled from '@emotion/styled';
+
+interface BackButtonStyleProps {
+ $style?: React.CSSProperties;
+}
+
+export const BackButtonWrapper = styled.div`
+ display: flex;
+ justify-content: flex-start;
+ width: 100%;
+
+ ${({ $style }) => $style && { ...$style }}
+`;
+
+export const BackButton = styled.button`
+ width: 3.5rem;
+ height: 3.5rem;
+
+ ${({ $style }) => $style && { ...$style }}
+`;
diff --git a/frontend/src/pages/HomePage/components/CopyTextButton/index.tsx b/frontend/src/components/common/CopyTextButton/index.tsx
similarity index 100%
rename from frontend/src/pages/HomePage/components/CopyTextButton/index.tsx
rename to frontend/src/components/common/CopyTextButton/index.tsx
diff --git a/frontend/src/pages/HomePage/components/CopyTextButton/styles.ts b/frontend/src/components/common/CopyTextButton/styles.ts
similarity index 100%
rename from frontend/src/pages/HomePage/components/CopyTextButton/styles.ts
rename to frontend/src/components/common/CopyTextButton/styles.ts
diff --git a/frontend/src/components/common/EmptyContent/index.tsx b/frontend/src/components/common/EmptyContent/index.tsx
new file mode 100644
index 000000000..4bad95f0d
--- /dev/null
+++ b/frontend/src/components/common/EmptyContent/index.tsx
@@ -0,0 +1,28 @@
+import Icon from '@/assets/emptyContentIcon.svg';
+import { EssentialPropsWithChildren } from '@/types';
+
+import * as S from './styles';
+
+interface EmptyContentProps {
+ iconHeight?: string;
+ iconWidth?: string;
+ iconMessageGap?: string;
+ messageFontSize?: string;
+}
+
+const EmptyContent = ({
+ iconHeight,
+ iconWidth,
+ iconMessageGap,
+ messageFontSize,
+ children,
+}: EssentialPropsWithChildren) => {
+ return (
+
+
+ {children}
+
+ );
+};
+
+export default EmptyContent;
diff --git a/frontend/src/components/common/EmptyContent/styles.ts b/frontend/src/components/common/EmptyContent/styles.ts
new file mode 100644
index 000000000..3a53166c2
--- /dev/null
+++ b/frontend/src/components/common/EmptyContent/styles.ts
@@ -0,0 +1,23 @@
+import styled from '@emotion/styled';
+
+export const EmptyContent = styled.div<{ $iconMessageGap?: string }>`
+ display: flex;
+ flex-direction: column;
+ gap: ${(props) => props.$iconMessageGap ?? '3.2rem'};
+ align-items: center;
+`;
+interface ImgProps {
+ $height?: string;
+ $width?: string;
+}
+export const Img = styled.img`
+ aspect-ratio: 39/25;
+ width: ${(props) => props.$width || 'auto'};
+ height: ${(props) => props.$height || (props.$width ? 'auto' : '19.7rem')};
+`;
+
+export const MessageContainer = styled.div<{ $messageFontSize?: string }>`
+ font-size: ${(props) => props.$messageFontSize ?? props.theme.fontSize.medium};
+ font-weight: ${({ theme }) => theme.fontWeight.semibold};
+ color: ${({ theme }) => theme.colors.emptyContentText};
+`;
diff --git a/frontend/src/components/common/NavigationTab/NavItem/index.tsx b/frontend/src/components/common/NavigationTab/NavItem/index.tsx
new file mode 100644
index 000000000..88d4f9682
--- /dev/null
+++ b/frontend/src/components/common/NavigationTab/NavItem/index.tsx
@@ -0,0 +1,17 @@
+import * as S from './styles';
+
+interface NavItemProps {
+ label: string;
+ $isSelected: boolean;
+ onClick: () => void;
+}
+
+const NavItem = ({ label, $isSelected, onClick }: NavItemProps) => {
+ return (
+
+
+
+ );
+};
+
+export default NavItem;
diff --git a/frontend/src/components/common/NavigationTab/NavItem/styles.ts b/frontend/src/components/common/NavigationTab/NavItem/styles.ts
new file mode 100644
index 000000000..f7f144cd2
--- /dev/null
+++ b/frontend/src/components/common/NavigationTab/NavItem/styles.ts
@@ -0,0 +1,33 @@
+import styled from '@emotion/styled';
+
+import media from '@/utils/media';
+
+interface NavItemProps {
+ $isSelected: boolean;
+}
+
+export const NavItem = styled.li`
+ border-bottom: 0.3rem solid ${({ theme, $isSelected }) => ($isSelected ? theme.colors.primary : 'none')};
+ padding: 0 1rem 1.3rem 1rem;
+
+ button {
+ font-weight: ${({ theme }) => theme.fontWeight.semibold};
+ color: ${({ theme, $isSelected }) => ($isSelected ? theme.colors.black : theme.colors.disabled)};
+
+ &:hover {
+ color: ${({ theme }) => theme.colors.black};
+ }
+ }
+
+ ${media.xSmall} {
+ display: flex;
+ flex: 1;
+ justify-content: center;
+
+ margin: 0 2rem;
+ }
+
+ ${media.xxSmall} {
+ margin: 0 1.6rem;
+ }
+`;
diff --git a/frontend/src/components/common/NavigationTab/index.tsx b/frontend/src/components/common/NavigationTab/index.tsx
new file mode 100644
index 000000000..2ad073e60
--- /dev/null
+++ b/frontend/src/components/common/NavigationTab/index.tsx
@@ -0,0 +1,27 @@
+import useNavigationTabs from '@/hooks/useNavigationTabs';
+
+import NavItem from './NavItem';
+import * as S from './styles';
+
+const NavigationTab = () => {
+ const { currentTabIndex, tabList } = useNavigationTabs();
+
+ return (
+
+
+ {tabList.map((tab, index) => {
+ return (
+