diff --git a/src/main/kotlin/land/leets/domain/application/presentation/ApplicationController.kt b/src/main/kotlin/land/leets/domain/application/presentation/ApplicationController.kt index 24ecde5..d7d8c71 100644 --- a/src/main/kotlin/land/leets/domain/application/presentation/ApplicationController.kt +++ b/src/main/kotlin/land/leets/domain/application/presentation/ApplicationController.kt @@ -10,13 +10,13 @@ import land.leets.domain.application.domain.Application import land.leets.domain.application.presentation.dto.ApplicationDetailsResponse import land.leets.domain.application.presentation.dto.ApplicationRequest import land.leets.domain.application.presentation.dto.ApplicationResponse +import land.leets.domain.application.presentation.dto.ApplicationStatusResponse import land.leets.domain.application.presentation.dto.StatusRequest import land.leets.domain.application.usecase.* import land.leets.domain.auth.AuthDetails import land.leets.global.error.ErrorResponse import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.* -import java.util.* @RestController @RequestMapping("/application") @@ -25,7 +25,8 @@ class ApplicationController( private val updateApplication: UpdateApplication, private val getApplication: GetAllApplication, private val getApplicationDetails: GetApplicationDetails, - private val updateResult: UpdateResult + private val updateResult: UpdateResult, + private val getApplicationStatus: GetApplicationStatus, ) { @Operation(summary = "(유저) 지원서 작성", description = "지원서를 작성합니다.") @@ -116,4 +117,18 @@ class ApplicationController( val uid = authDetails.uid return getApplicationDetails.execute(uid) } + + @Operation(summary = "(유저) 지원서 상태 불러오기", description = "작성한 지원서 상태를 불러옵니다.") + @ApiResponses( + ApiResponse(responseCode = "200"), + ApiResponse(responseCode = "400", content = [Content(schema = Schema(implementation = ErrorResponse::class))]), + ApiResponse(responseCode = "403", content = [Content(schema = Schema(implementation = ErrorResponse::class))]), + ApiResponse(responseCode = "404", content = [Content(schema = Schema(implementation = ErrorResponse::class))]), + ApiResponse(responseCode = "500", content = [Content(schema = Schema(implementation = ErrorResponse::class))]) + ) + @GetMapping("/status") + fun getStatus(@AuthenticationPrincipal authDetails: AuthDetails): ApplicationStatusResponse { + val uid = authDetails.uid + return getApplicationStatus.execute(uid) + } } diff --git a/src/main/kotlin/land/leets/domain/application/presentation/dto/ApplicationStatusResponse.kt b/src/main/kotlin/land/leets/domain/application/presentation/dto/ApplicationStatusResponse.kt new file mode 100644 index 0000000..ad3b426 --- /dev/null +++ b/src/main/kotlin/land/leets/domain/application/presentation/dto/ApplicationStatusResponse.kt @@ -0,0 +1,39 @@ +package land.leets.domain.application.presentation.dto + +import land.leets.domain.application.domain.Application +import land.leets.domain.application.type.ApplicationStatus +import land.leets.domain.interview.domain.Interview +import land.leets.domain.interview.type.HasInterview +import java.time.LocalDateTime + +data class ApplicationStatusResponse( + val id: Long, + val status: ApplicationStatus, + val hasInterview: HasInterview?, + val interviewDate: LocalDateTime?, + val interviewPlace: String?, +) { + companion object { + fun of( + application: Application, + interview: Interview?, + ): ApplicationStatusResponse { + if (application.applicationStatus != ApplicationStatus.PASS_PAPER) { + return ApplicationStatusResponse( + id = application.id!!, + status = application.applicationStatus, + hasInterview = null, + interviewDate = null, + interviewPlace = null, + ) + } + return ApplicationStatusResponse( + id = application.id!!, + status = application.applicationStatus, + hasInterview = interview!!.hasInterview, + interviewDate = interview.fixedInterviewDate, + interviewPlace = interview.place, + ) + } + } +} diff --git a/src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatus.kt b/src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatus.kt new file mode 100644 index 0000000..488d57c --- /dev/null +++ b/src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatus.kt @@ -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 +} diff --git a/src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatusImpl.kt b/src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatusImpl.kt new file mode 100644 index 0000000..b4788be --- /dev/null +++ b/src/main/kotlin/land/leets/domain/application/usecase/GetApplicationStatusImpl.kt @@ -0,0 +1,24 @@ +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 land.leets.domain.interview.domain.repository.InterviewRepository +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, + private val interviewRepository: InterviewRepository, +) : GetApplicationStatus { + override fun execute(uid: UUID): ApplicationStatusResponse { + val application = applicationRepository.findByUser_Id(uid) + ?: throw ApplicationNotFoundException() + val interview = interviewRepository.findByApplication(application) + + return ApplicationStatusResponse.of(application, interview) + } +} diff --git a/src/test/kotlin/land/leets/domain/application/usecase/GetApplicationStatusImplTest.kt b/src/test/kotlin/land/leets/domain/application/usecase/GetApplicationStatusImplTest.kt new file mode 100644 index 0000000..7afbe7f --- /dev/null +++ b/src/test/kotlin/land/leets/domain/application/usecase/GetApplicationStatusImplTest.kt @@ -0,0 +1,97 @@ +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 land.leets.domain.interview.domain.Interview +import land.leets.domain.interview.domain.repository.InterviewRepository +import land.leets.domain.interview.type.HasInterview +import java.time.LocalDateTime +import java.util.UUID + +class GetApplicationStatusImplTest : DescribeSpec({ + + val applicationRepository = mockk() + val interviewRepository = mockk() + val getApplicationStatus = GetApplicationStatusImpl(applicationRepository, interviewRepository) + + val uid = UUID.randomUUID() + + val interviewDate: LocalDateTime = LocalDateTime.of(2026, 3, 14, 14, 0) + val interviewPlace = "전자정보도서관 1층 스터디룸 A" + val applicationId = 1L + + fun mockApplication( + status: ApplicationStatus, + ): Application = mockk().also { application -> + every { application.id } returns applicationId + every { application.applicationStatus } returns status + } + + fun mockInterview( + hasInterview: HasInterview = HasInterview.PENDING, + ): Interview = mockk().also { interview -> + every { interview.hasInterview } returns hasInterview + every { interview.fixedInterviewDate } returns interviewDate + every { interview.place } returns interviewPlace + } + + describe("GetApplicationStatusImpl 유스케이스는") { + + context("지원서 상태 조회를 요청할 때") { + + it("지원서 상태가 PASS_PAPER이면 인터뷰 정보를 포함하여 반환한다") { + val application = mockApplication(status = ApplicationStatus.PASS_PAPER) + val interview = mockInterview() + every { applicationRepository.findByUser_Id(uid) } returns application + every { interviewRepository.findByApplication(application) } returns interview + + val result = getApplicationStatus.execute(uid) + + result.id shouldBe 1L + result.status shouldBe ApplicationStatus.PASS_PAPER + result.hasInterview shouldBe HasInterview.PENDING + result.interviewDate shouldBe interviewDate + result.interviewPlace shouldBe interviewPlace + } + + it("PASS_PAPER이 아닌 상태들은 인터뷰 정보가 null이어야 한다") { + val nonInterviewStatuses = listOf( + ApplicationStatus.PENDING, + ApplicationStatus.FAIL_PAPER, + ApplicationStatus.PASS, + ApplicationStatus.FAIL, + ) + + nonInterviewStatuses.forEach { status -> + val application = mockApplication(status = status) + val interview = mockInterview() + every { applicationRepository.findByUser_Id(uid) } returns application + every { interviewRepository.findByApplication(application) } returns interview + + val result = getApplicationStatus.execute(uid) + + result.id shouldBe 1L + result.status shouldBe status + result.hasInterview shouldBe null + result.interviewDate shouldBe null + result.interviewPlace shouldBe null + } + } + + it("지원서가 존재하지 않으면 ApplicationNotFoundException을 던진다") { + every { applicationRepository.findByUser_Id(uid) } returns null + + shouldThrow { + getApplicationStatus.execute(uid) + } + } + } + } +})