Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Page<Interview> findAllByUserAndReviewStatusIn(
AND i.industry = :industry
AND i.jobCategory = :jobCategory
""")
List<Interview> findAllSimilarInterviewsByUser(User user, Industry interview, JobCategory jobCategory);
List<Interview> findAllSimilarInterviewsByUser(User user, Industry industry, JobCategory jobCategory);

@Query("""
SELECT i
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.shyashyashya.refit.domain.interview.model.InterviewReviewStatus.LOG_DRAFT;
import static com.shyashyashya.refit.domain.interview.model.InterviewReviewStatus.NOT_LOGGED;
import static com.shyashyashya.refit.domain.interview.model.InterviewReviewStatus.QNA_SET_DRAFT;
import static com.shyashyashya.refit.domain.interview.model.InterviewReviewStatus.SELF_REVIEW_DRAFT;

import com.shyashyashya.refit.domain.interview.dto.response.DashboardCalendarResponse;
Expand Down Expand Up @@ -35,7 +36,7 @@
public class DashboardService {

private static final List<InterviewReviewStatus> REVIEW_NEEDED_STATUSES =
List.of(NOT_LOGGED, LOG_DRAFT, SELF_REVIEW_DRAFT);
List.of(NOT_LOGGED, LOG_DRAFT, QNA_SET_DRAFT, SELF_REVIEW_DRAFT);

private final RequestUserContext requestUserContext;
private final InterviewRepository interviewRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

import java.sql.ResultSet;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.Instant;
import java.util.ArrayList;
Expand All @@ -57,6 +58,7 @@
@SpringBootTest(webEnvironment = RANDOM_PORT)
public abstract class IntegrationTest {

protected static final LocalDateTime NOW = LocalDateTime.of(2026, 2, 16, 10, 0, 0);
protected static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

@LocalServerPort
Expand Down Expand Up @@ -224,6 +226,16 @@ protected Company createAndSaveCompany(String companyName) {
return companyRepository.save(company);
}

protected Industry createAndSaveIndustry(String industryName) {
Industry industry = Industry.create(industryName);
return industryRepository.save(industry);
}

protected JobCategory createAndSaveJobCategory(String jobCategoryName) {
JobCategory jobCategory = JobCategory.create(jobCategoryName);
return jobCategoryRepository.save(jobCategory);
}

protected QnaSet createAndSaveQnaSet(QnaSetCreateRequest request, Interview interview) {
return createAndSaveQnaSet(request, interview, false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
package com.shyashyashya.refit.interview.integration;

import static com.shyashyashya.refit.global.model.ResponseCode.COMMON200;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;

import com.shyashyashya.refit.core.IntegrationTest;
import com.shyashyashya.refit.domain.interview.dto.request.InterviewCreateRequest;
import com.shyashyashya.refit.domain.interview.model.Interview;
import com.shyashyashya.refit.domain.interview.model.InterviewReviewStatus;
import com.shyashyashya.refit.domain.interview.model.InterviewType;
import com.shyashyashya.refit.domain.qnaset.model.QnaSet;
import com.shyashyashya.refit.domain.qnaset.repository.QnaSetRepository;
import java.time.LocalDateTime;
import java.util.List;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.beans.factory.annotation.Autowired;

public class DashboardIntegrationTest extends IntegrationTest {

@Autowired
private QnaSetRepository qnaSetRepository;

@Nested
class 대시보드_헤드라인_조회 {

private final String path = "/dashboard/headline";

@Test
void 면접_일정이_없는_경우_REGISTER_INTERVIEW_타입을_반환한다() {
// given

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.headlineType", equalTo("REGISTER_INTERVIEW"));
}

@Test
void 일주일_내_예정된_면접이_있는_경우_PREPARE_INTERVIEW_타입을_반환한다() {
// given
LocalDateTime upcomingDate = NOW.plusDays(3);
var request = new InterviewCreateRequest(upcomingDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(request, InterviewReviewStatus.NOT_LOGGED);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.headlineType", equalTo("PREPARE_INTERVIEW"))
.body("result.upcomingInterviewDday", notNullValue());
}

@ParameterizedTest
@EnumSource(value = InterviewReviewStatus.class, names = {"NOT_LOGGED", "LOG_DRAFT", "QNA_SET_DRAFT", "SELF_REVIEW_DRAFT"})
void 일주일_내_예정된_면접이_있고_아직_복기하지_않은_면접도_있는_경우_PREPARE_INTERVIEW_타입을_반환한다(InterviewReviewStatus reviewStatus) {
// given
LocalDateTime upcomingDate = NOW.plusDays(3);
var upcomingRequest = new InterviewCreateRequest(upcomingDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(upcomingRequest, InterviewReviewStatus.NOT_LOGGED);

LocalDateTime pastDate = NOW.minusDays(3);
var request = new InterviewCreateRequest(pastDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(request, reviewStatus);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.headlineType", equalTo("PREPARE_INTERVIEW"))
.body("result.upcomingInterviewDday", notNullValue());
}

@ParameterizedTest
@EnumSource(value = InterviewReviewStatus.class, names = {"NOT_LOGGED", "LOG_DRAFT", "QNA_SET_DRAFT", "SELF_REVIEW_DRAFT"})
void 복기를_완료하지_않은_면접이_있는_경우_REVIEW_INTERVIEW_타입을_반환한다(InterviewReviewStatus reviewStatus) {
// given
LocalDateTime pastDate = NOW.minusDays(3);
var request = new InterviewCreateRequest(pastDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(request, reviewStatus);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.headlineType", equalTo("REVIEW_INTERVIEW"));
}

@ParameterizedTest
@EnumSource(value = InterviewReviewStatus.class, names = {"NOT_LOGGED", "LOG_DRAFT", "QNA_SET_DRAFT", "SELF_REVIEW_DRAFT"})
void 일주일_바깥에_예정된_면접이_있고_복기를_완료하지_않은_면접이_있는_경우_REVIEW_INTERVIEW_타입을_반환한다(InterviewReviewStatus reviewStatus) {
// given
LocalDateTime futureDate = NOW.plusDays(10);
var futureInterviewRequest = new InterviewCreateRequest(futureDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(futureInterviewRequest, InterviewReviewStatus.DEBRIEF_COMPLETED);

LocalDateTime pastDate = NOW.minusDays(3);
var request = new InterviewCreateRequest(pastDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(request, reviewStatus);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.headlineType", equalTo("REVIEW_INTERVIEW"));
}

@Test
void 예정된_면접이_없고_모든_면접을_복기_완료했다면_CHECK_INTERVIEW_HISTORY_타입을_반환한다() {
// given
LocalDateTime pastDate = NOW.minusDays(10);
var request = new InterviewCreateRequest(pastDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(request, InterviewReviewStatus.DEBRIEF_COMPLETED);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.headlineType", equalTo("CHECK_INTERVIEW_HISTORY"));
}

@Test
void 일주일_바깥에_예정된_면접이_있고_모든_면접을_복기_완료했다면_CHECK_INTERVIEW_HISTORY_타입을_반환한다() {
// given
LocalDateTime futureDate = NOW.plusDays(10);
var futureInterviewRequest = new InterviewCreateRequest(futureDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(futureInterviewRequest, InterviewReviewStatus.DEBRIEF_COMPLETED);

LocalDateTime pastDate = NOW.minusDays(10);
var request = new InterviewCreateRequest(pastDate, InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer");
createAndSaveInterview(request, InterviewReviewStatus.DEBRIEF_COMPLETED);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.headlineType", equalTo("CHECK_INTERVIEW_HISTORY"));
}
}

@Nested
class 대시보드_캘린더_조회 {

private final String path = "/dashboard/calendar/interview";

@Test
void 특정_월의_면접_일정을_조회한다() {
// given
LocalDateTime startOfMonth = LocalDateTime.of(NOW.getYear(), NOW.getMonth(), 1, 10, 0);
LocalDateTime endOfMonth = startOfMonth.plusMonths(1).minusDays(1);

int id1 = createAndSaveInterview(
new InterviewCreateRequest(
startOfMonth,
InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.NOT_LOGGED).getId().intValue();

int id2 = createAndSaveInterview(
new InterviewCreateRequest(
endOfMonth,
InterviewType.SECOND, company2.getName(), industry1.getId(), jobCategory1.getId(), "Engineer"
), InterviewReviewStatus.NOT_LOGGED).getId().intValue();

int id3 = createAndSaveInterview(
new InterviewCreateRequest(
endOfMonth,
InterviewType.SECOND, company2.getName(), industry1.getId(), jobCategory1.getId(), "Engineer"
), InterviewReviewStatus.NOT_LOGGED).getId().intValue();

createAndSaveInterview(
new InterviewCreateRequest(
startOfMonth.minusDays(1),
InterviewType.SECOND, company2.getName(), industry1.getId(), jobCategory1.getId(), "Engineer"
), InterviewReviewStatus.NOT_LOGGED);

createAndSaveInterview(
new InterviewCreateRequest(
endOfMonth.plusDays(1),
InterviewType.SECOND, company2.getName(), industry1.getId(), jobCategory1.getId(), "Engineer"
), InterviewReviewStatus.NOT_LOGGED);

// when & then
given(spec)
.queryParam("year", NOW.getYear())
.queryParam("month", NOW.getMonthValue())
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result", hasSize(2))
.body("result*.interviews.interviewId", containsInAnyOrder(List.of(id1), List.of(id2, id3)));
}
}

@Nested
class 곧_있을_면접_조회 {

private final String path = "/dashboard/interview/upcoming";

@Test
void 곧_있을_면접_리스트를_조회한다() {
// given
LocalDateTime upcomingDate = NOW.plusDays(2);

createAndSaveInterview(
new InterviewCreateRequest(
upcomingDate,
InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.NOT_LOGGED);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.content", hasSize(1))
.body("result.content[0].upcomingInterview.companyName", equalTo(company1.getName()));
}
}

@Nested
class 내가_어렵게_느낀_질문_조회 {

private final String path = "/dashboard/qna-set/my/difficult";

@Test
void 어렵다고_표시한_QnA_리스트를_조회한다() {
// given
Interview interview = createAndSaveInterview(
new InterviewCreateRequest(
NOW.minusDays(5),
InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), InterviewReviewStatus.DEBRIEF_COMPLETED);

QnaSet difficultQna = QnaSet.create("Question 1", "Answer 1", true, interview, null);
QnaSet easyQna = QnaSet.create("Question 2", "Answer 2", false, interview, null);
qnaSetRepository.save(difficultQna);
qnaSetRepository.save(easyQna);
Comment on lines +267 to +270
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

테스트 데이터 생성을 위해 qnaSetRepository를 직접 사용하고 있습니다. IntegrationTest 기본 클래스에 다른 엔티티들을 위한 createAndSave... 헬퍼 메서드가 있는 것처럼, QnaSet을 위한 헬퍼 메서드를 추가하면 테스트 코드의 일관성과 재사용성을 높일 수 있습니다.

IntegrationTest 클래스에 createAndSaveQnaSet과 같은 헬퍼 메서드를 만들고, 이 테스트 클래스에서는 해당 메서드를 호출하는 방식으로 리팩토링하는 것을 고려해보세요. 이렇게 하면 테스트 준비(given) 단계가 더 간결해지고, 다른 테스트에서도 쉽게 QnaSet 테스트 데이터를 생성할 수 있습니다.

Suggested change
QnaSet difficultQna = QnaSet.create("Question 1", "Answer 1", true, interview, null);
QnaSet easyQna = QnaSet.create("Question 2", "Answer 2", false, interview, null);
qnaSetRepository.save(difficultQna);
qnaSetRepository.save(easyQna);
createAndSaveQnaSet("Question 1", "Answer 1", true, interview);
createAndSaveQnaSet("Question 2", "Answer 2", false, interview);


// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.content", hasSize(1))
.body("result.content[0].question", equalTo("Question 1"));
}
}

@Nested
class 복기_대기중인_면접_조회 {

private final String path = "/dashboard/interview/debrief-uncompleted";

@ParameterizedTest
@EnumSource(value = InterviewReviewStatus.class, names = {"NOT_LOGGED", "LOG_DRAFT", "QNA_SET_DRAFT", "SELF_REVIEW_DRAFT"})
void 복기가_완료되지_않은_면접_리스트를_조회한다(InterviewReviewStatus reviewStatus) {
// given
int interviewId = createAndSaveInterview(
new InterviewCreateRequest(
NOW.minusDays(1),
InterviewType.FIRST, company1.getName(), industry1.getId(), jobCategory1.getId(), "Developer"
), reviewStatus).getId().intValue();

createAndSaveInterview(
new InterviewCreateRequest(
NOW.minusDays(2),
InterviewType.SECOND, company2.getName(), industry1.getId(), jobCategory1.getId(), "Engineer"
), InterviewReviewStatus.DEBRIEF_COMPLETED);

// when & then
given(spec)
.when()
.get(path)
.then()
.statusCode(200)
.body("code", equalTo(COMMON200.name()))
.body("result.content", hasSize(1))
.body("result.content[0].interview.interviewId", equalTo(interviewId));
}
}
}
Loading