diff --git a/src/main/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewPersonaSummary.java b/src/main/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewPersonaSummary.java
new file mode 100644
index 00000000..69bb3242
--- /dev/null
+++ b/src/main/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewPersonaSummary.java
@@ -0,0 +1,40 @@
+package repit.repit_api_server.domain.userdata.interview.dto.response;
+
+import repit.repit_api_server.domain.userdata.persona.entity.PersonaEntity;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.InterviewTone;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Level;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Role;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Type;
+
+/**
+ * 면접 응답에 붙는 대표 면접관. 목록·상세 화면이 성향·난이도·면접관 이름을 여기서 읽는다.
+ *
+ *
1:1은 그 면접의 면접관, N:1은 진행 순서 맨 앞 면접관이다. 피드백 응답의 성향·난이도와
+ * 같은 규칙이다. 이름은 저장된 값 그대로다 — 1:1 면접관 이름 끝의 유일성 접미사는 화면이 뗀다.
+ */
+public record InterviewPersonaSummary(
+ Long id,
+ String personaName,
+ Role personaRole,
+ Type type,
+ Level level,
+ InterviewTone tone,
+ String imageUrl
+) {
+
+ /** 면접관이 없으면(지워졌거나 걸려 있지 않으면) null이다. */
+ public static InterviewPersonaSummary of(PersonaEntity persona) {
+ if (persona == null) {
+ return null;
+ }
+ return new InterviewPersonaSummary(
+ persona.getPersonaId(),
+ persona.getPersonaName(),
+ persona.getRole(),
+ persona.getType(),
+ persona.getLevel(),
+ persona.getTone(),
+ persona.getImageUrl()
+ );
+ }
+}
diff --git a/src/main/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewResponse.java b/src/main/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewResponse.java
index d8118345..faac1489 100644
--- a/src/main/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewResponse.java
+++ b/src/main/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewResponse.java
@@ -8,6 +8,7 @@
import repit.repit_api_server.domain.userdata.interview.entity.InterviewEntity;
import repit.repit_api_server.domain.userdata.interview.entity.enums.InterviewMode;
import repit.repit_api_server.domain.userdata.interview.entity.enums.Status;
+import repit.repit_api_server.domain.userdata.persona.entity.PersonaEntity;
import java.time.LocalDateTime;
import java.util.List;
@@ -24,23 +25,26 @@ public class InterviewResponse {
private Long personaId;
// N:1 면접관 목록. 진행 순서대로다.
private List personaIds;
+ // 대표 면접관. 1:1은 그 면접관, N:1은 진행 순서 맨 앞 면접관이다. 면접관이 없으면 비어 있다.
+ private InterviewPersonaSummary persona;
private String sessionId;
private Status status;
private LocalDateTime createdAt;
// 면접을 만들 때 고른 기업. 고르지 않았으면 비어 있다.
private CompanyBadge company;
- public static InterviewResponse from(InterviewEntity interview) {
- return from(interview, List.of());
- }
-
- public static InterviewResponse from(InterviewEntity interview, List personaIds) {
+ /**
+ * @param personaIds N:1 면접관 목록. 1:1은 빈 목록이다.
+ * @param persona 대표 면접관. 없으면 null이다.
+ */
+ public static InterviewResponse from(InterviewEntity interview, List personaIds, PersonaEntity persona) {
return new InterviewResponse(
interview.getInterviewId(),
interview.getUserId(),
interview.getMode(),
interview.getPersonaId(),
personaIds,
+ InterviewPersonaSummary.of(persona),
interview.getSessionId(),
interview.getStatus(),
interview.getCreatedAt(),
diff --git a/src/main/java/repit/repit_api_server/domain/userdata/interview/service/InterviewService.java b/src/main/java/repit/repit_api_server/domain/userdata/interview/service/InterviewService.java
index 241ec7af..aa00ea2b 100644
--- a/src/main/java/repit/repit_api_server/domain/userdata/interview/service/InterviewService.java
+++ b/src/main/java/repit/repit_api_server/domain/userdata/interview/service/InterviewService.java
@@ -93,7 +93,7 @@ public InterviewResponse createInterview(Long userId, CreateInterviewRequest req
.build();
InterviewEntity saved = interviewRepository.save(interview);
- return InterviewResponse.from(saved);
+ return InterviewResponse.from(saved, List.of(), persona);
}
/**
@@ -138,7 +138,9 @@ private InterviewResponse createMultiInterview(Long userId, List requested
}
interviewPersonaRepository.saveAll(members);
- return InterviewResponse.from(saved, ordered.stream().map(PersonaEntity::getPersonaId).toList());
+ // 대표 면접관은 진행 순서 맨 앞이다. 조회 응답과 같은 규칙이다.
+ return InterviewResponse.from(saved, ordered.stream().map(PersonaEntity::getPersonaId).toList(),
+ ordered.getFirst());
}
/**
@@ -261,9 +263,47 @@ private static void requireSupportedJob(AuthUser authUser) {
}
}
+ /**
+ * 사용자의 면접 목록. 면접마다 대표 면접관을 붙인다.
+ *
+ * N:1 면접관 명단과 면접관은 면접마다 따로 읽지 않고 한 번에 읽어 면접별로 나눈다.
+ * 그러지 않으면 목록이 길어지는 만큼 쿼리가 늘어난다.
+ */
public List getAllInterviewsByUserId(Long userId) {
- return interviewRepository.findAllByUserId(userId).stream()
- .map(interview -> InterviewResponse.from(interview, personaIdsOf(interview)))
+ List interviews = interviewRepository.findAllByUserId(userId);
+
+ List multiInterviewIds = interviews.stream()
+ .filter(interview -> interview.getMode() == InterviewMode.MULTI)
+ .map(InterviewEntity::getInterviewId)
+ .toList();
+ Map> personaIdsByInterview = multiInterviewIds.isEmpty()
+ ? Map.of()
+ : interviewPersonaRepository
+ .findAllByInterviewIdInOrderByInterviewIdAscPersonaOrderAsc(multiInterviewIds).stream()
+ .collect(Collectors.groupingBy(InterviewPersonaEntity::getInterviewId,
+ Collectors.mapping(InterviewPersonaEntity::getPersonaId, Collectors.toList())));
+
+ Map representativeIdByInterview = new HashMap<>();
+ for (InterviewEntity interview : interviews) {
+ Long personaId = representativePersonaId(interview,
+ personaIdsByInterview.getOrDefault(interview.getInterviewId(), List.of()));
+ if (personaId != null) {
+ representativeIdByInterview.put(interview.getInterviewId(), personaId);
+ }
+ }
+ Map personaById = representativeIdByInterview.isEmpty()
+ ? Map.of()
+ : personaRepository.findAllById(new LinkedHashSet<>(representativeIdByInterview.values())).stream()
+ .collect(Collectors.toMap(PersonaEntity::getPersonaId, Function.identity()));
+
+ return interviews.stream()
+ .map(interview -> {
+ Long representativeId = representativeIdByInterview.get(interview.getInterviewId());
+ return InterviewResponse.from(
+ interview,
+ personaIdsByInterview.getOrDefault(interview.getInterviewId(), List.of()),
+ representativeId == null ? null : personaById.get(representativeId));
+ })
.toList();
}
@@ -271,7 +311,25 @@ public InterviewResponse getInterviewById(Long userId, Long interviewId) {
InterviewEntity interview = interviewRepository.findById(interviewId)
.orElseThrow(() -> BusinessException.notFound("면접을 찾을 수 없습니다"));
verifyOwner(interview, userId);
- return InterviewResponse.from(interview, personaIdsOf(interview));
+ List personaIds = personaIdsOf(interview);
+ Long representativeId = representativePersonaId(interview, personaIds);
+ PersonaEntity persona = representativeId == null
+ ? null
+ : personaRepository.findById(representativeId).orElse(null);
+ return InterviewResponse.from(interview, personaIds, persona);
+ }
+
+ /**
+ * 면접을 대표하는 면접관 id. 1:1은 그 면접의 면접관, N:1은 진행 순서 맨 앞 면접관이다.
+ * 피드백 응답이 성향·난이도를 대표 한 명으로 읽는 것과 같은 규칙이다. 걸린 면접관이 없으면 null이다.
+ *
+ * @param multiPersonaIds 진행 순서대로 정렬된 N:1 면접관 명단. 1:1은 빈 목록이다.
+ */
+ private static Long representativePersonaId(InterviewEntity interview, List multiPersonaIds) {
+ if (interview.getMode() == InterviewMode.MULTI) {
+ return multiPersonaIds.isEmpty() ? null : multiPersonaIds.getFirst();
+ }
+ return interview.getPersonaId();
}
/**
diff --git a/src/test/java/repit/repit_api_server/domain/userdata/interview/controller/InterviewControllerCreateTest.java b/src/test/java/repit/repit_api_server/domain/userdata/interview/controller/InterviewControllerCreateTest.java
index 1c6d052f..20948860 100644
--- a/src/test/java/repit/repit_api_server/domain/userdata/interview/controller/InterviewControllerCreateTest.java
+++ b/src/test/java/repit/repit_api_server/domain/userdata/interview/controller/InterviewControllerCreateTest.java
@@ -96,7 +96,7 @@ void tearDown() {
}
private static InterviewResponse response(Long interviewId) {
- return new InterviewResponse(interviewId, USER_ID, InterviewMode.SOLO, 1L, List.of(), "session",
+ return new InterviewResponse(interviewId, USER_ID, InterviewMode.SOLO, 1L, List.of(), null, "session",
Status.IN_PROGRESS, LocalDateTime.now(), new CompanyBadge(12L, "샘플커머스"));
}
diff --git a/src/test/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewResponseSerializationTest.java b/src/test/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewResponseSerializationTest.java
new file mode 100644
index 00000000..54edafbd
--- /dev/null
+++ b/src/test/java/repit/repit_api_server/domain/userdata/interview/dto/response/InterviewResponseSerializationTest.java
@@ -0,0 +1,83 @@
+package repit.repit_api_server.domain.userdata.interview.dto.response;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import repit.repit_api_server.domain.userdata.interview.entity.InterviewEntity;
+import repit.repit_api_server.domain.userdata.interview.entity.enums.InterviewMode;
+import repit.repit_api_server.domain.userdata.interview.entity.enums.Status;
+import repit.repit_api_server.domain.userdata.persona.entity.PersonaEntity;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Gender;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.InterviewTone;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Level;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Role;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Type;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ObjectMapper;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** 웹이 읽는 persona 키 이름이 그대로 나가야 한다. 이름이 어긋나면 화면은 조용히 "미제공"이 된다. */
+class InterviewResponseSerializationTest {
+
+ private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class));
+
+ private static final InterviewEntity INTERVIEW = InterviewEntity.builder()
+ .interviewId(56L)
+ .userId(7L)
+ .personaId(56L)
+ .mode(InterviewMode.SOLO)
+ .sessionId("sess-56")
+ .status(Status.COMPLETED)
+ .build();
+
+ @Test
+ void 대표_면접관을_persona_객체로_내보낸다() {
+ PersonaEntity persona = PersonaEntity.builder()
+ .personaId(56L)
+ .personaName("김도윤-4898e87d")
+ .role(Role.TECH)
+ .type(Type.FRIENDLY)
+ .level(Level.EASY)
+ .tone(InterviewTone.GENTLE)
+ .career(3)
+ .gender(Gender.MALE)
+ .imageUrl("/img/56.svg")
+ .build();
+
+ contextRunner.run(context -> {
+ ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
+
+ JsonNode json = objectMapper.valueToTree(InterviewResponse.from(INTERVIEW, List.of(), persona));
+ JsonNode node = json.get("persona");
+
+ assertThat(node.propertyNames()).containsExactlyInAnyOrder(
+ "id", "personaName", "personaRole", "type", "level", "tone", "imageUrl");
+ assertThat(node.get("id").asLong()).isEqualTo(56L);
+ assertThat(node.get("personaName").asString()).isEqualTo("김도윤-4898e87d");
+ assertThat(node.get("personaRole").asString()).isEqualTo("TECH");
+ assertThat(node.get("type").asString()).isEqualTo("FRIENDLY");
+ assertThat(node.get("level").asString()).isEqualTo("EASY");
+ assertThat(node.get("tone").asString()).isEqualTo("GENTLE");
+ assertThat(node.get("imageUrl").asString()).isEqualTo("/img/56.svg");
+ // 기존 필드는 그대로 남는다.
+ assertThat(json.get("personaId").asLong()).isEqualTo(56L);
+ });
+ }
+
+ @Test
+ void 면접관이_없으면_persona는_null이다() {
+ contextRunner.run(context -> {
+ ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
+
+ JsonNode json = objectMapper.valueToTree(InterviewResponse.from(INTERVIEW, List.of(), null));
+
+ assertThat(json.has("persona")).isTrue();
+ assertThat(json.get("persona").isNull()).isTrue();
+ });
+ }
+}
diff --git a/src/test/java/repit/repit_api_server/domain/userdata/interview/service/InterviewServicePersonaTest.java b/src/test/java/repit/repit_api_server/domain/userdata/interview/service/InterviewServicePersonaTest.java
new file mode 100644
index 00000000..856c950d
--- /dev/null
+++ b/src/test/java/repit/repit_api_server/domain/userdata/interview/service/InterviewServicePersonaTest.java
@@ -0,0 +1,227 @@
+package repit.repit_api_server.domain.userdata.interview.service;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import repit.repit_api_server.domain.userdata.answer.repository.AnswerRepository;
+import repit.repit_api_server.domain.userdata.interview.dto.response.InterviewPersonaSummary;
+import repit.repit_api_server.domain.userdata.interview.dto.response.InterviewResponse;
+import repit.repit_api_server.domain.userdata.interview.entity.InterviewEntity;
+import repit.repit_api_server.domain.userdata.interview.entity.InterviewPersonaEntity;
+import repit.repit_api_server.domain.userdata.interview.entity.enums.InterviewMode;
+import repit.repit_api_server.domain.userdata.interview.entity.enums.Status;
+import repit.repit_api_server.domain.userdata.interview.repository.InterviewPersonaRepository;
+import repit.repit_api_server.domain.userdata.interview.repository.InterviewRepository;
+import repit.repit_api_server.domain.userdata.persona.entity.PersonaEntity;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Gender;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.InterviewTone;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Level;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Role;
+import repit.repit_api_server.domain.userdata.persona.entity.enums.Type;
+import repit.repit_api_server.domain.userdata.persona.repository.PersonaRepository;
+import repit.repit_api_server.domain.userdata.question.repository.QuestionRepository;
+import repit.repit_api_server.domain.userdata.question.service.QuestionTailorService;
+import repit.repit_api_server.global.client.ChatServerClient;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyIterable;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * 면접 조회 응답의 대표 면접관(persona).
+ *
+ * 웹 목록·상세는 성향·난이도·면접관 이름을 여기서 읽는다. 피드백이 아직 없는 면접도
+ * 면접관은 정해져 있으므로 값이 나와야 한다. 1:1은 그 면접관, N:1은 진행 순서 맨 앞,
+ * 면접관이 없으면 null이다.
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class InterviewServicePersonaTest {
+
+ @Mock
+ private InterviewRepository interviewRepository;
+ @Mock
+ private QuestionRepository questionRepository;
+ @Mock
+ private ChatServerClient chatServerClient;
+ @Mock
+ private AnswerRepository answerRepository;
+ @Mock
+ private PersonaRepository personaRepository;
+ @Mock
+ private QuestionTailorService questionTailorService;
+ @Mock
+ private InterviewPersonaRepository interviewPersonaRepository;
+
+ private InterviewService service;
+
+ private static final Long USER_ID = 7L;
+
+ @BeforeEach
+ void setUp() {
+ service = new InterviewService(interviewRepository, questionRepository, chatServerClient,
+ answerRepository, personaRepository, questionTailorService,
+ interviewPersonaRepository, null, null);
+ }
+
+ @Test
+ void 일대일_단건은_그_면접의_면접관을_싣는다() {
+ when(interviewRepository.findById(3L)).thenReturn(Optional.of(solo(3L, 56L)));
+ when(personaRepository.findById(56L)).thenReturn(Optional.of(
+ persona(56L, "김도윤-4898e87d", Role.TECH, Type.FRIENDLY, Level.EASY, InterviewTone.GENTLE)));
+
+ InterviewPersonaSummary persona = service.getInterviewById(USER_ID, 3L).getPersona();
+
+ // 이름은 저장값 그대로다. 접미사는 화면이 뗀다.
+ assertThat(persona).isEqualTo(new InterviewPersonaSummary(56L, "김도윤-4898e87d", Role.TECH,
+ Type.FRIENDLY, Level.EASY, InterviewTone.GENTLE, "/img/56.svg"));
+ }
+
+ @Test
+ void N대1_단건은_진행_순서_맨_앞_면접관을_싣는다() {
+ when(interviewRepository.findById(4L)).thenReturn(Optional.of(multi(4L)));
+ when(interviewPersonaRepository.findAllByInterviewIdOrderByPersonaOrderAsc(4L)).thenReturn(List.of(
+ member(4L, 11L, 0), member(4L, 12L, 1)));
+ when(personaRepository.findById(11L)).thenReturn(Optional.of(
+ persona(11L, "박기술", Role.TECH, Type.METICULOUS, Level.HARD, InterviewTone.DIRECT)));
+
+ InterviewResponse response = service.getInterviewById(USER_ID, 4L);
+
+ assertThat(response.getPersonaIds()).containsExactly(11L, 12L);
+ assertThat(response.getPersona()).isEqualTo(new InterviewPersonaSummary(11L, "박기술", Role.TECH,
+ Type.METICULOUS, Level.HARD, InterviewTone.DIRECT, "/img/11.svg"));
+ verify(personaRepository, never()).findById(12L);
+ }
+
+ @Test
+ void 면접관이_걸려_있지_않으면_null이다() {
+ when(interviewRepository.findById(3L)).thenReturn(Optional.of(solo(3L, null)));
+ when(interviewRepository.findById(4L)).thenReturn(Optional.of(multi(4L)));
+ when(interviewPersonaRepository.findAllByInterviewIdOrderByPersonaOrderAsc(4L)).thenReturn(List.of());
+
+ assertThat(service.getInterviewById(USER_ID, 3L).getPersona()).isNull();
+ assertThat(service.getInterviewById(USER_ID, 4L).getPersona()).isNull();
+ verify(personaRepository, never()).findById(any());
+ }
+
+ @Test
+ void 면접관이_지워졌으면_null이다() {
+ when(interviewRepository.findById(3L)).thenReturn(Optional.of(solo(3L, 56L)));
+ when(personaRepository.findById(56L)).thenReturn(Optional.empty());
+
+ assertThat(service.getInterviewById(USER_ID, 3L).getPersona()).isNull();
+ }
+
+ /**
+ * 목록은 면접관을 면접마다 읽지 않고 한 번에 읽는다. N:1 명단도 마찬가지다.
+ * 1:1·N:1·면접관 없음이 섞여 있어도 각자 맞는 값이 붙어야 한다.
+ */
+ @Test
+ void 목록은_면접관을_한_번에_읽어_면접마다_대표_면접관을_싣는다() {
+ when(interviewRepository.findAllByUserId(USER_ID)).thenReturn(List.of(
+ solo(1L, 56L), multi(2L), solo(3L, null), multi(4L)));
+ when(interviewPersonaRepository.findAllByInterviewIdInOrderByInterviewIdAscPersonaOrderAsc(List.of(2L, 4L)))
+ .thenReturn(List.of(member(2L, 11L, 0), member(2L, 12L, 1)));
+ Map stored = Map.of(
+ 56L, persona(56L, "김도윤-4898e87d", Role.TECH, Type.FRIENDLY, Level.EASY, InterviewTone.GENTLE),
+ 11L, persona(11L, "박기술", Role.TECH, Type.METICULOUS, Level.HARD, InterviewTone.DIRECT),
+ 12L, persona(12L, "이인사", Role.HR, Type.METICULOUS, Level.HARD, InterviewTone.DIRECT));
+ when(personaRepository.findAllById(anyIterable())).thenAnswer(invocation -> {
+ Iterable ids = invocation.getArgument(0);
+ return StreamSupport.stream(ids.spliterator(), false)
+ .map(stored::get)
+ .filter(java.util.Objects::nonNull)
+ .toList();
+ });
+
+ Map byId = service.getAllInterviewsByUserId(USER_ID).stream()
+ .collect(Collectors.toMap(InterviewResponse::getInterviewId, Function.identity()));
+
+ assertThat(byId.get(1L).getPersona().id()).isEqualTo(56L);
+ assertThat(byId.get(1L).getPersona().level()).isEqualTo(Level.EASY);
+ assertThat(byId.get(1L).getPersonaIds()).isEmpty();
+ assertThat(byId.get(2L).getPersona().id()).isEqualTo(11L);
+ assertThat(byId.get(2L).getPersonaIds()).containsExactly(11L, 12L);
+ assertThat(byId.get(3L).getPersona()).isNull();
+ // 명단이 비어 있는 N:1도 깨지지 않고 비어 나간다.
+ assertThat(byId.get(4L).getPersona()).isNull();
+ assertThat(byId.get(4L).getPersonaIds()).isEmpty();
+
+ verify(personaRepository, times(1)).findAllById(anyIterable());
+ verify(personaRepository, never()).findById(any());
+ verify(interviewPersonaRepository, times(1))
+ .findAllByInterviewIdInOrderByInterviewIdAscPersonaOrderAsc(any());
+ verify(interviewPersonaRepository, never()).findAllByInterviewIdOrderByPersonaOrderAsc(any());
+ }
+
+ @Test
+ void 대표_면접관이_하나도_없으면_면접관을_읽지_않는다() {
+ when(interviewRepository.findAllByUserId(USER_ID)).thenReturn(List.of(solo(1L, null)));
+
+ assertThat(service.getAllInterviewsByUserId(USER_ID))
+ .singleElement()
+ .extracting(InterviewResponse::getPersona)
+ .isNull();
+ verify(personaRepository, never()).findAllById(anyIterable());
+ verify(interviewPersonaRepository, never())
+ .findAllByInterviewIdInOrderByInterviewIdAscPersonaOrderAsc(any());
+ }
+
+ private static InterviewEntity solo(Long interviewId, Long personaId) {
+ return InterviewEntity.builder()
+ .interviewId(interviewId)
+ .userId(USER_ID)
+ .personaId(personaId)
+ .mode(InterviewMode.SOLO)
+ .sessionId("sess-" + interviewId)
+ .status(Status.COMPLETED)
+ .build();
+ }
+
+ private static InterviewEntity multi(Long interviewId) {
+ return InterviewEntity.builder()
+ .interviewId(interviewId)
+ .userId(USER_ID)
+ .mode(InterviewMode.MULTI)
+ .sessionId("sess-" + interviewId)
+ .status(Status.COMPLETED)
+ .build();
+ }
+
+ private static InterviewPersonaEntity member(Long interviewId, Long personaId, int order) {
+ return InterviewPersonaEntity.builder()
+ .interviewId(interviewId)
+ .personaId(personaId)
+ .personaOrder(order)
+ .build();
+ }
+
+ private static PersonaEntity persona(Long id, String name, Role role, Type type, Level level, InterviewTone tone) {
+ return PersonaEntity.builder()
+ .personaId(id)
+ .personaName(name)
+ .role(role)
+ .type(type)
+ .level(level)
+ .tone(tone)
+ .career(3)
+ .gender(Gender.MALE)
+ .imageUrl("/img/" + id + ".svg")
+ .build();
+ }
+}