Skip to content

Commit 8ebf365

Browse files
authored
테스트 기록 초기화 API를 구현한다. (#104)
feat: implement exam history delete API
1 parent 8dfda89 commit 8ebf365

File tree

12 files changed

+165
-14
lines changed

12 files changed

+165
-14
lines changed

api/src/main/kotlin/com/gotchai/api/presentation/v1/admin/AdminController.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import com.gotchai.api.global.annotation.ApiV1Controller
44
import com.gotchai.api.presentation.v1.admin.request.CreateExamRequest
55
import com.gotchai.api.presentation.v1.exam.response.ExamResponse
66
import com.gotchai.domain.admin.port.`in`.AdminCommandUseCase
7+
import org.springframework.web.bind.annotation.DeleteMapping
78
import org.springframework.web.bind.annotation.ModelAttribute
9+
import org.springframework.web.bind.annotation.PathVariable
810
import org.springframework.web.bind.annotation.PostMapping
911

1012
@ApiV1Controller
@@ -19,4 +21,14 @@ class AdminController(
1921
adminCommandUseCase
2022
.createExam(request.toCommand())
2123
.let { ExamResponse.from(it) }
24+
25+
@DeleteMapping("/admin/users/{userId}/exams/{examId}/histories")
26+
fun deleteExamHistory(
27+
@PathVariable
28+
userId: Long,
29+
@PathVariable
30+
examId: Long
31+
) {
32+
adminCommandUseCase.deleteExamHistoryByExamIdAndUserId(examId, userId)
33+
}
2234
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.gotchai.api.presentation.v1.admin
2+
3+
import com.gotchai.api.common.ControllerTest
4+
import com.gotchai.api.docs.createExamRequestFields
5+
import com.gotchai.api.docs.examResponseFields
6+
import com.gotchai.api.fixture.createCreateExamRequest
7+
import com.gotchai.api.global.dto.ApiResponse
8+
import com.gotchai.api.presentation.v1.exam.response.ExamResponse
9+
import com.gotchai.api.util.bodyForm
10+
import com.gotchai.api.util.desc
11+
import com.gotchai.api.util.document
12+
import com.gotchai.api.util.expectError
13+
import com.gotchai.domain.admin.port.`in`.AdminCommandUseCase
14+
import com.gotchai.domain.exam.exception.ExamHistoryNotFoundException
15+
import com.gotchai.domain.fixture.ID
16+
import com.gotchai.domain.fixture.createExam
17+
import com.ninjasquad.springmockk.MockkBean
18+
import io.mockk.every
19+
import io.mockk.just
20+
import io.mockk.runs
21+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
22+
import org.springframework.test.web.reactive.server.expectBody
23+
24+
@WebMvcTest(AdminController::class)
25+
class AdminControllerTest : ControllerTest() {
26+
@MockkBean
27+
private lateinit var adminCommandUseCase: AdminCommandUseCase
28+
29+
init {
30+
describe("createExam()은") {
31+
val request =
32+
createCreateExamRequest()
33+
.also {
34+
every { adminCommandUseCase.createExam(any()) } returns createExam()
35+
}
36+
37+
it("상태 코드 200과 ExamResponse를 반환한다.") {
38+
webClient
39+
.post()
40+
.uri("/api/v1/admin/exams")
41+
.bodyForm(request)
42+
.exchange()
43+
.expectStatus()
44+
.isOk
45+
.expectBody<ApiResponse<ExamResponse>>()
46+
.document("테스트 생성 성공(200)") {
47+
requestForm(createExamRequestFields)
48+
responseBody(examResponseFields)
49+
}
50+
}
51+
}
52+
53+
describe("deleteExamHistory()는") {
54+
context("테스트 기록이 존재하는 경우") {
55+
every { adminCommandUseCase.deleteExamHistoryByExamIdAndUserId(ID, ID) } just runs
56+
57+
it("상태 코드 200을 반환한다.") {
58+
webClient
59+
.delete()
60+
.uri("/api/v1/admin/users/{userId}/exams/{examId}/histories", ID, ID)
61+
.exchange()
62+
.expectStatus()
63+
.isOk
64+
.expectBody<Void>()
65+
.document("테스트 기록 삭제 성공(200)") {
66+
pathParams(
67+
"userId" desc "유저 식별자",
68+
"examId" desc "테스트 식별자"
69+
)
70+
}
71+
}
72+
}
73+
74+
context("테스트 기록이 존재하지 않는 경우") {
75+
every { adminCommandUseCase.deleteExamHistoryByExamIdAndUserId(ID, ID) } throws ExamHistoryNotFoundException()
76+
77+
it("상태 코드 404를 반환한다.") {
78+
webClient
79+
.delete()
80+
.uri("/api/v1/admin/users/{userId}/exams/{examId}/histories", ID, ID)
81+
.exchange()
82+
.expectStatus()
83+
.isNotFound
84+
.expectError()
85+
.document("테스트 기록 삭제 실패(404)") {
86+
pathParams(
87+
"userId" desc "유저 식별자",
88+
"examId" desc "테스트 식별자"
89+
)
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}

domain/src/main/kotlin/com/gotchai/domain/admin/adapter/in/AdminCommandService.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@ import com.gotchai.domain.admin.dto.command.CreateExamCommand
44
import com.gotchai.domain.admin.exception.InvalidFileException
55
import com.gotchai.domain.admin.port.`in`.AdminCommandUseCase
66
import com.gotchai.domain.exam.entity.Exam
7+
import com.gotchai.domain.exam.exception.ExamHistoryNotFoundException
78
import com.gotchai.domain.exam.port.out.ExamCommandPort
9+
import com.gotchai.domain.exam.port.out.ExamHistoryCommandPort
10+
import com.gotchai.domain.exam.port.out.ExamHistoryQueryPort
811
import com.gotchai.domain.global.provider.ObjectStorageProvider
12+
import com.gotchai.domain.quiz.port.out.QuizHistoryCommandPort
913
import org.springframework.stereotype.Service
1014
import org.springframework.transaction.annotation.Transactional
1115
import java.util.*
1216

1317
@Service
1418
class AdminCommandService(
1519
private val examCommandPort: ExamCommandPort,
20+
private val examHistoryQueryPort: ExamHistoryQueryPort,
21+
private val examHistoryCommandPort: ExamHistoryCommandPort,
22+
private val quizHistoryCommandPort: QuizHistoryCommandPort,
1623
private val objectStorageProvider: ObjectStorageProvider
1724
) : AdminCommandUseCase {
1825
@Transactional
@@ -36,4 +43,16 @@ class AdminCommandService(
3643
)
3744
)
3845
}
46+
47+
@Transactional
48+
override fun deleteExamHistoryByExamIdAndUserId(
49+
examId: Long,
50+
userId: Long
51+
) {
52+
val examHistory =
53+
examHistoryQueryPort.getExamHistoryByExamIdAndUserId(examId, userId) ?: throw ExamHistoryNotFoundException()
54+
55+
quizHistoryCommandPort.deleteQuizHistoriesByExamHistoryId(examHistory.id)
56+
examHistoryCommandPort.deleteExamHistoryByExamIdAndUserId(examId, userId)
57+
}
3958
}

domain/src/main/kotlin/com/gotchai/domain/admin/port/in/AdminCommandUseCase.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,9 @@ import com.gotchai.domain.exam.entity.Exam
55

66
interface AdminCommandUseCase {
77
fun createExam(command: CreateExamCommand): Exam
8+
9+
fun deleteExamHistoryByExamIdAndUserId(
10+
examId: Long,
11+
userId: Long
12+
)
813
}

domain/src/main/kotlin/com/gotchai/domain/exam/adapter/in/ExamCommandService.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.gotchai.domain.exam.port.`in`.ExamCommandUseCase
1616
import com.gotchai.domain.exam.port.out.ExamHistoryCommandPort
1717
import com.gotchai.domain.exam.port.out.ExamHistoryQueryPort
1818
import com.gotchai.domain.exam.port.out.ExamQueryPort
19+
import com.gotchai.domain.quiz.port.out.QuizHistoryCommandPort
1920
import com.gotchai.domain.quiz.port.out.QuizHistoryQueryPort
2021
import com.gotchai.domain.quiz.port.out.QuizQueryPort
2122
import org.springframework.stereotype.Service
@@ -28,6 +29,7 @@ class ExamCommandService(
2829
private val examHistoryQueryPort: ExamHistoryQueryPort,
2930
private val examHistoryCommandPort: ExamHistoryCommandPort,
3031
private val quizHistoryQueryPort: QuizHistoryQueryPort,
32+
private val quizHistoryCommandPort: QuizHistoryCommandPort,
3133
private val userBadgeCommandPort: UserBadgeCommandPort,
3234
private val badgeQueryPort: BadgeQueryPort
3335
) : ExamCommandUseCase {
@@ -54,7 +56,7 @@ class ExamCommandService(
5456
} else {
5557
if (examHistory.isSolved) throw ExamAlreadySolvedException()
5658

57-
quizHistoryQueryPort.deleteQuizHistoriesByExamHistoryId(examHistory.id)
59+
quizHistoryCommandPort.deleteQuizHistoriesByExamHistoryId(examHistory.id)
5860
examHistoryCommandPort.updateExamHistory(
5961
examHistory.copy(
6062
quizIds = quizIds,

domain/src/main/kotlin/com/gotchai/domain/exam/port/out/ExamHistoryCommandPort.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@ interface ExamHistoryCommandPort {
66
fun createExamHistory(creation: ExamHistory.Creation): ExamHistory
77

88
fun updateExamHistory(examHistory: ExamHistory): ExamHistory
9+
10+
fun deleteExamHistoryByExamIdAndUserId(
11+
examId: Long,
12+
userId: Long
13+
)
914
}

domain/src/main/kotlin/com/gotchai/domain/quiz/port/out/QuizHistoryCommandPort.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ import com.gotchai.domain.quiz.entity.QuizHistory
44

55
interface QuizHistoryCommandPort {
66
fun createQuizHistory(creation: QuizHistory.Creation): QuizHistory
7+
8+
fun deleteQuizHistoriesByExamHistoryId(examHistoryId: Long)
79
}

domain/src/main/kotlin/com/gotchai/domain/quiz/port/out/QuizHistoryQueryPort.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,4 @@ import com.gotchai.domain.quiz.entity.QuizHistory
44

55
interface QuizHistoryQueryPort {
66
fun getQuizHistoriesByExamHistoryId(examHistoryId: Long): List<QuizHistory>
7-
8-
fun deleteQuizHistoriesByExamHistoryId(examHistoryId: Long)
97
}

storage/rdb/src/main/kotlin/com/gotchai/storage/rdb/exam/adapter/out/ExamHistoryCommandAdapter.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.gotchai.storage.rdb.exam.entity.ExamHistoryEntity
66
import com.gotchai.storage.rdb.exam.repository.ExamHistoryJpaRepository
77
import com.gotchai.storage.rdb.global.annotation.Adapter
88
import com.gotchai.storage.rdb.global.util.findByIdOrElseThrow
9-
import org.springframework.transaction.annotation.Transactional
109

1110
@Adapter
1211
class ExamHistoryCommandAdapter(
@@ -17,7 +16,6 @@ class ExamHistoryCommandAdapter(
1716
.save(ExamHistoryEntity.from(creation))
1817
.toExamHistory()
1918

20-
@Transactional
2119
override fun updateExamHistory(examHistory: ExamHistory): ExamHistory =
2220
with(examHistory) {
2321
val examHistoryEntity = examHistoryJpaRepository.findByIdOrElseThrow(id)
@@ -32,4 +30,11 @@ class ExamHistoryCommandAdapter(
3230
.save(examHistoryEntity)
3331
.toExamHistory()
3432
}
33+
34+
override fun deleteExamHistoryByExamIdAndUserId(
35+
examId: Long,
36+
userId: Long
37+
) {
38+
examHistoryJpaRepository.deleteByExamIdAndUserId(examId, userId)
39+
}
3540
}

storage/rdb/src/main/kotlin/com/gotchai/storage/rdb/exam/repository/ExamHistoryJpaRepository.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,25 @@ interface ExamHistoryJpaRepository : JpaRepository<ExamHistoryEntity, Long> {
1313

1414
@Query("SELECT e FROM ExamHistoryEntity e WHERE e.examId = :examId AND e.isSolved = :isSolved")
1515
fun findAllByExamIdAndIsSolved(
16-
@Param("examId") examId: Long,
17-
@Param("isSolved") isSolved: Boolean
16+
@Param("examId")
17+
examId: Long,
18+
@Param("isSolved")
19+
isSolved: Boolean
1820
): List<ExamHistoryEntity>
1921

2022
@Query("SELECT e FROM ExamHistoryEntity e WHERE e.userId = :userId AND e.isSolved = :isSolved")
2123
fun findAllByUserIdAndIsSolved(
22-
@Param("userId") userId: Long,
23-
@Param("isSolved") isSolved: Boolean
24+
@Param("userId")
25+
userId: Long,
26+
@Param("isSolved")
27+
isSolved: Boolean
2428
): List<ExamHistoryEntity>
2529

2630
@Query("SELECT e FROM ExamHistoryEntity e JOIN FETCH e.quizIds")
2731
fun findAllWithQuizIds(): List<ExamHistoryEntity>
32+
33+
fun deleteByExamIdAndUserId(
34+
examId: Long,
35+
userId: Long
36+
): List<ExamHistoryEntity>
2837
}

0 commit comments

Comments
 (0)