-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 합불 결과 조회 기능 구현 #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package land.leets.domain.application.presentation.dto | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import land.leets.domain.application.domain.Application | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import land.leets.domain.application.type.ApplicationStatus | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data class ApplicationStatusResponse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val id: Long, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val status: ApplicationStatus, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val interviewDay: String?, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val interviewTime: String?, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| companion object { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun from( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| application: Application, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): ApplicationStatusResponse { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (application.applicationStatus != ApplicationStatus.PASS_PAPER) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ApplicationStatusResponse( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id = application.id!!, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status = application.applicationStatus, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interviewDay = null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interviewTime = null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val interviewDay: String?, | |
| val interviewTime: String?, | |
| ) { | |
| companion object { | |
| fun from( | |
| application: Application, | |
| ): ApplicationStatusResponse { | |
| if (application.applicationStatus != ApplicationStatus.PASS_PAPER) { | |
| return ApplicationStatusResponse( | |
| id = application.id!!, | |
| status = application.applicationStatus, | |
| interviewDay = null, | |
| interviewTime = null, | |
| ) | |
| } | |
| val interviewDay: String, | |
| val interviewTime: String, | |
| ) { | |
| companion object { | |
| fun from( | |
| application: Application, | |
| ): ApplicationStatusResponse { |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of the non-null assertion operator (!!) on application.id could throw a NullPointerException if the application entity hasn't been persisted yet. While this is unlikely in this context since we're fetching from the repository, consider using a safer approach or document the assumption that the application will always have an ID at this point.
A safer alternative would be to use the elvis operator with a meaningful exception: id = application.id ?: throw IllegalStateException("Application must have an ID")
| id = application.id!!, | |
| status = application.applicationStatus, | |
| interviewDay = null, | |
| interviewTime = null, | |
| ) | |
| } | |
| return ApplicationStatusResponse( | |
| id = application.id!!, | |
| id = application.id ?: throw IllegalStateException("Application must have an ID"), | |
| status = application.applicationStatus, | |
| interviewDay = null, | |
| interviewTime = null, | |
| ) | |
| } | |
| return ApplicationStatusResponse( | |
| id = application.id ?: throw IllegalStateException("Application must have an ID"), |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same non-null assertion operator (!!) issue appears here. See the comment on line 18 for the recommended safer approach.
| if (application.applicationStatus != ApplicationStatus.PASS_PAPER) { | |
| return ApplicationStatusResponse( | |
| id = application.id!!, | |
| status = application.applicationStatus, | |
| interviewDay = null, | |
| interviewTime = null, | |
| ) | |
| } | |
| return ApplicationStatusResponse( | |
| id = application.id!!, | |
| val applicationId = application.id | |
| ?: throw IllegalStateException("Application id must not be null") | |
| if (application.applicationStatus != ApplicationStatus.PASS_PAPER) { | |
| return ApplicationStatusResponse( | |
| id = applicationId, | |
| status = application.applicationStatus, | |
| interviewDay = null, | |
| interviewTime = null, | |
| ) | |
| } | |
| return ApplicationStatusResponse( | |
| id = applicationId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 현재 로직을 서류 합격 상태일 경우에는 인터뷰 장소와 날짜를 반환하고,
그렇지 않으면 지원 상태(서탈, 최합, 최탈)만 반환하는 구조로 이해했습니다.
다만 저희 서비스에서는
Application엔티티는 지원자가 희망한 면접 정보를 가지고 있고,
운영진이 최종적으로 통보하는 면접 정보는 Interview 엔티티에서 관리하는 것으로 알고 있습니다.
따라서 인터뷰에 대한 정보는 Application 엔티티가 아닌 Interview 엔티티에서 가지고 오는 것이 옳은 로직이라고 생각하는데, 정완님의 의견이 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오아 제가 해당 부분은 완전히 착각하고 코드를 작성해버렸네요..캐치 감사합니다..!!
interview 엔티티에서 정보를 가져오는게 정답이네요 ㅎㅎ
수정하겠습니다 ❤️
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package land.leets.domain.application.usecase | ||
|
|
||
| import land.leets.domain.application.presentation.dto.ApplicationStatusResponse | ||
| import java.util.UUID | ||
|
|
||
| interface GetApplicationStatus { | ||
| fun execute(uid: UUID): ApplicationStatusResponse | ||
| } | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package land.leets.domain.application.usecase | ||
|
|
||
| import land.leets.domain.application.domain.repository.ApplicationRepository | ||
| import land.leets.domain.application.exception.ApplicationNotFoundException | ||
| import land.leets.domain.application.presentation.dto.ApplicationStatusResponse | ||
| import org.springframework.stereotype.Service | ||
| import org.springframework.transaction.annotation.Transactional | ||
| import java.util.UUID | ||
|
|
||
| @Service | ||
| @Transactional(readOnly = true) | ||
| class GetApplicationStatusImpl( | ||
| private val applicationRepository: ApplicationRepository, | ||
| ) : GetApplicationStatus { | ||
| override fun execute(uid: UUID): ApplicationStatusResponse { | ||
| val application = applicationRepository.findByUser_Id(uid) | ||
| ?: throw ApplicationNotFoundException() | ||
|
|
||
| return ApplicationStatusResponse.from(application) | ||
| } | ||
| } | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| package land.leets.domain.application.usecase | ||
|
|
||
| import io.kotest.assertions.throwables.shouldThrow | ||
| import io.kotest.core.spec.style.DescribeSpec | ||
| import io.kotest.matchers.shouldBe | ||
| import io.mockk.every | ||
| import io.mockk.mockk | ||
| import land.leets.domain.application.domain.Application | ||
| import land.leets.domain.application.domain.repository.ApplicationRepository | ||
| import land.leets.domain.application.exception.ApplicationNotFoundException | ||
| import land.leets.domain.application.type.ApplicationStatus | ||
| import java.util.UUID | ||
|
|
||
| class GetApplicationStatusImplTest : DescribeSpec({ | ||
|
|
||
| val applicationRepository = mockk<ApplicationRepository>() | ||
| val getApplicationStatus = GetApplicationStatusImpl(applicationRepository) | ||
|
|
||
| val uid = UUID.randomUUID() | ||
|
|
||
| val interviewDay = "2025년 3월 3일 (토)" | ||
| val interviewTime = "15:00" | ||
|
|
||
| fun mockApplication( | ||
| id: Long = 1L, | ||
| status: ApplicationStatus, | ||
| day: String = interviewDay, | ||
| time: String = interviewTime | ||
| ): Application = mockk<Application>().also { application -> | ||
| every { application.id } returns id | ||
| every { application.applicationStatus } returns status | ||
| every { application.interviewDay } returns day | ||
| every { application.interviewTime } returns time | ||
| } | ||
|
|
||
| describe("GetApplicationStatusImpl 유스케이스는") { | ||
|
|
||
| context("지원서 상태 조회를 요청할 때") { | ||
|
|
||
| it("지원서 상태가 PASS_PAPER이면 인터뷰 정보를 포함하여 반환한다") { | ||
| val application = mockApplication(status = ApplicationStatus.PASS_PAPER) | ||
| every { applicationRepository.findByUser_Id(uid) } returns application | ||
|
|
||
| val result = getApplicationStatus.execute(uid) | ||
|
|
||
| result.id shouldBe 1L | ||
| result.status shouldBe ApplicationStatus.PASS_PAPER | ||
| result.interviewDay shouldBe interviewDay | ||
| result.interviewTime shouldBe interviewTime | ||
| } | ||
|
|
||
| it("PASS_PAPER이 아닌 상태들은 인터뷰 정보가 null이어야 한다") { | ||
| val nonInterviewStatuses = listOf( | ||
| ApplicationStatus.PENDING, | ||
| ApplicationStatus.FAIL_PAPER, | ||
| ApplicationStatus.PASS, | ||
| ApplicationStatus.FAIL, | ||
| ) | ||
|
|
||
| nonInterviewStatuses.forEach { status -> | ||
| val application = mockApplication(status = status) | ||
| every { applicationRepository.findByUser_Id(uid) } returns application | ||
|
|
||
| val result = getApplicationStatus.execute(uid) | ||
|
|
||
| result.id shouldBe 1L | ||
| result.status shouldBe status | ||
| result.interviewDay shouldBe null | ||
| result.interviewTime shouldBe null | ||
| } | ||
| } | ||
|
|
||
| it("지원서가 존재하지 않으면 ApplicationNotFoundException을 던진다") { | ||
| every { applicationRepository.findByUser_Id(uid) } returns null | ||
|
|
||
| shouldThrow<ApplicationNotFoundException> { | ||
| getApplicationStatus.execute(uid) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The conditional logic for returning interview information based on application status would benefit from a comment explaining the business rule. This would help future maintainers understand why interview details are only included for PASS_PAPER status.
Consider adding a brief comment like: "// Interview details are only shown to applicants who passed the paper screening"