Skip to content

Commit 99609f3

Browse files
authored
내가 푼 테스트 조회 API를 구현한다. (#62)
feat: implement my exam read
1 parent 39e3455 commit 99609f3

File tree

13 files changed

+181
-13
lines changed

13 files changed

+181
-13
lines changed

api/src/main/kotlin/com/gotchai/api/presentation/v1/exam/ExamController.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ class ExamController(
2222
fun getExamById(
2323
@AuthenticationPrincipal
2424
userId: Long,
25-
@PathVariable(name = "id") examId: Long
25+
@PathVariable(name = "id")
26+
examId: Long
2627
): ExamDetailResponse = ExamDetailResponse.from(examQueryUseCase.getExamById(examId))
28+
29+
@GetMapping("/users/me/exam/solved")
30+
fun getMyExams(
31+
@AuthenticationPrincipal
32+
userId: Long
33+
): ExamListResponse =
34+
examQueryUseCase.getExamsByUserId(userId)
35+
.let { ExamListResponse.from(it) }
2736
}

api/src/test/kotlin/com/gotchai/api/presentation/v1/exam/ExamControllerTest.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import com.gotchai.domain.global.exception.NotFoundDataException
1818
import com.ninjasquad.springmockk.MockkBean
1919
import io.mockk.every
2020
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
21-
import org.springframework.restdocs.payload.PayloadDocumentation.responseBody
2221
import org.springframework.test.web.reactive.server.expectBody
2322

2423
@WebMvcTest(ExamController::class)
@@ -66,6 +65,26 @@ class ExamControllerTest : ControllerTest() {
6665
}
6766
}
6867

68+
describe("getMyExams()는") {
69+
val exams =
70+
listOf(createExam())
71+
.also {
72+
every { examQueryUseCase.getExamsByUserId(ID) } returns it
73+
}
74+
75+
it("상태 코드 200과 ExamListResponse를 반환한다.") {
76+
webClient.get()
77+
.uri("/api/v1/users/me/exam/solved")
78+
.exchange()
79+
.expectStatus()
80+
.isOk
81+
.expectBody<ApiResponse<ExamListResponse>>()
82+
.document("내가 푼 테스트 조회 성공(200)") {
83+
responseBody(examListResponseFields)
84+
}
85+
}
86+
}
87+
6988
describe("getExamById()는") {
7089
context("조회하려는 테스트가 존재하는 경우") {
7190
val result = createGetExamResult()

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import com.gotchai.domain.exam.dto.result.GetExamResult
44
import com.gotchai.domain.exam.entity.Exam
55
import com.gotchai.domain.exam.port.`in`.ExamQueryUseCase
66
import com.gotchai.domain.exam.port.out.ExamQueryPort
7+
import com.gotchai.domain.exam.port.out.ExamResultQueryPort
78
import com.gotchai.domain.quiz.port.out.QuizQueryPort
89
import org.springframework.stereotype.Service
910

1011
@Service
1112
class ExamQueryService(
1213
private val examQueryPort: ExamQueryPort,
14+
private val examResultQueryPort: ExamResultQueryPort,
1315
private val quizQueryPort: QuizQueryPort
1416
) : ExamQueryUseCase {
1517
override fun getExamById(examId: Long): GetExamResult {
@@ -19,5 +21,12 @@ class ExamQueryService(
1921
return GetExamResult.of(exam, quizzes.map { it.id })
2022
}
2123

24+
override fun getExamsByUserId(userId: Long): List<Exam> {
25+
val examResults = examResultQueryPort.getExamResultsByUserId(userId)
26+
val exams = examQueryPort.getExamsByInIn(examResults.map { it.id })
27+
28+
return exams
29+
}
30+
2231
override fun getExams(): List<Exam> = examQueryPort.getExams()
2332
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.gotchai.domain.exam.entity
2+
3+
import java.time.LocalDateTime
4+
5+
data class ExamResult(
6+
val id: Long,
7+
val examId: Long,
8+
val userId: Long,
9+
val answerCount: Int,
10+
val createdAt: LocalDateTime
11+
) {
12+
data class Creation(
13+
val examId: Long,
14+
val userId: Long,
15+
val answerCount: Int
16+
)
17+
}

domain/src/main/kotlin/com/gotchai/domain/exam/port/in/ExamQueryUseCase.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@ import com.gotchai.domain.exam.entity.Exam
66
interface ExamQueryUseCase {
77
fun getExamById(examId: Long): GetExamResult
88

9+
fun getExamsByUserId(userId: Long): List<Exam>
10+
911
fun getExams(): List<Exam>
1012
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ interface ExamQueryPort {
66
fun getExamById(examId: Long): Exam
77

88
fun getExams(): List<Exam>
9+
10+
fun getExamsByInIn(ids: Collection<Long>): List<Exam>
911
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.gotchai.domain.exam.port.out
2+
3+
import com.gotchai.domain.exam.entity.ExamResult
4+
5+
interface ExamResultQueryPort {
6+
fun getExamResultsByUserId(userId: Long): List<ExamResult>
7+
}

domain/src/testFixtures/kotlin/com/gotchai/domain/fixture/ExamFixture.kt

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@ package com.gotchai.domain.fixture
22

33
import com.gotchai.domain.exam.dto.result.GetExamResult
44
import com.gotchai.domain.exam.entity.Exam
5+
import com.gotchai.domain.exam.entity.ExamResult
56
import java.time.LocalDateTime
67

8+
const val TITLE = "AI와 크리스마스 파티"
9+
const val SUB_TITLE = "산타는 누구야?"
10+
const val DESCRIPTION_IMAGE = "https://gotchai-dev.s3.ap-northeast-2.amazonaws.com/exam/icon/image"
11+
const val ICON_IMAGE = "https://gotchai-dev.s3.ap-northeast-2.amazonaws.com/exam/description/image"
12+
const val THEME = "blue"
13+
const val ANSWER_COUNT = 1
14+
715
fun createExam(
816
id: Long = ID,
9-
title: String = "테스트 시험",
10-
subTitle: String = "테스트 부제목",
11-
descriptionImage: String = "https://example.com/description.jpg",
12-
iconImage: String = "https://example.com/icon.jpg",
13-
theme: String = "blue",
17+
title: String = TITLE,
18+
subTitle: String = SUB_TITLE,
19+
descriptionImage: String = DESCRIPTION_IMAGE,
20+
iconImage: String = ICON_IMAGE,
21+
theme: String = THEME,
1422
createdAt: LocalDateTime = CREATED_AT
1523
): Exam =
1624
Exam(
@@ -23,7 +31,38 @@ fun createExam(
2331
createdAt = createdAt
2432
)
2533

34+
fun createExamResult(
35+
id: Long = ID,
36+
examId: Long = ID,
37+
userId: Long = ID,
38+
answerCount: Int = ANSWER_COUNT,
39+
createdAt: LocalDateTime = CREATED_AT
40+
): ExamResult =
41+
ExamResult(
42+
id = id,
43+
examId = examId,
44+
userId = userId,
45+
answerCount = answerCount,
46+
createdAt = createdAt
47+
)
48+
2649
fun createGetExamResult(
27-
exam: Exam = createExam(),
28-
quizIds: List<Long> = listOf(1L, 2L, 3L, 4L, 5L, 6L, 7L)
29-
): GetExamResult = GetExamResult.of(exam, quizIds)
50+
id: Long = ID,
51+
title: String = TITLE,
52+
subTitle: String = SUB_TITLE,
53+
descriptionImage: String = DESCRIPTION_IMAGE,
54+
iconImage: String = ICON_IMAGE,
55+
theme: String = THEME,
56+
quizIds: List<Long> = listOf(ID),
57+
createdAt: LocalDateTime = CREATED_AT,
58+
): GetExamResult =
59+
GetExamResult(
60+
id = id,
61+
title = title,
62+
subTitle = subTitle,
63+
descriptionImage = descriptionImage,
64+
iconImage = iconImage,
65+
theme = theme,
66+
quizIds = quizIds,
67+
createdAt = createdAt
68+
)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ package com.gotchai.storage.rdb.exam.adapter.out
33
import com.gotchai.domain.exam.entity.Exam
44
import com.gotchai.domain.exam.port.out.ExamQueryPort
55
import com.gotchai.storage.rdb.exam.repository.ExamJpaRepository
6+
import com.gotchai.storage.rdb.global.annotation.Adapter
67
import com.gotchai.storage.rdb.global.annotation.ReadOnlyTransactional
78
import com.gotchai.storage.rdb.global.util.findByIdOrElseThrow
8-
import org.springframework.stereotype.Repository
99

10-
@Repository
10+
@Adapter
1111
class ExamQueryAdapter(
1212
private val examJpaRepository: ExamJpaRepository
1313
) : ExamQueryPort {
@@ -16,4 +16,9 @@ class ExamQueryAdapter(
1616

1717
@ReadOnlyTransactional
1818
override fun getExams(): List<Exam> = examJpaRepository.findAll().map { it.toExam() }
19+
20+
@ReadOnlyTransactional
21+
override fun getExamsByInIn(ids: Collection<Long>): List<Exam> =
22+
examJpaRepository.findByIdIn(ids)
23+
.map { it.toExam() }
1924
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.gotchai.storage.rdb.exam.adapter.out
2+
3+
import com.gotchai.domain.exam.entity.ExamResult
4+
import com.gotchai.domain.exam.port.out.ExamResultQueryPort
5+
import com.gotchai.storage.rdb.exam.repository.ExamResultJpaRepository
6+
import com.gotchai.storage.rdb.global.annotation.Adapter
7+
8+
@Adapter
9+
class ExamResultQueryAdapter(
10+
private val examResultJpaRepository: ExamResultJpaRepository
11+
) : ExamResultQueryPort {
12+
override fun getExamResultsByUserId(userId: Long): List<ExamResult> =
13+
examResultJpaRepository.findExamResultsByUserId(userId)
14+
.map { it.toExamResult() }
15+
}

0 commit comments

Comments
 (0)